Поскольку у вас уже есть ответ с использованием массива структуры фиксированного размера для хранения информации, переход от использования массива фиксированного размера, где хранилище автоматически резервируется для вас в стеке, к динамически распределенному хранилищу, где вы можете realloc
при необходимости просто требуется сначала объявить указатель на тип, а не массив типа, а затем выделить хранилище для каждой структуры.
Где раньше, с массивом фиксированного размера из 512 элементов у вас будет:
#define MAX_WORDS 512 /* max number of words */
...
/* Array of struct of distinct words, initialized all zero */
words_t words[MAX_WORDS] = {{ .word = "" }};
При динамическом размещении просто объявите указатель на тип и предоставьте начальное выделение некоторого разумного количества элементов, например
#define MAX_WORDS 8 /* initial number of struct to allocate */
...
/* pointer to allocated block of max_words struct initialized zero */
words_t *words = calloc (max_words, sizeof *words);
( примечание: вы можете выделить с помощью либо malloc, calloc or realloc
, но только calloc
выделяет, а также устанавливает все байты равными нулю. В вашем случае, так как вы хотите, чтобы .cap
и .count
члены инициализировались ноль, calloc
- разумный выбор)
Стоит немного остановиться, чтобы понять, используете ли вы массив фиксированного размера или выделенный блок памяти, вы обращаетесь к своим данным через указатель на первый элемент. Единственная реальная разница - это то, что компилятор резервирует хранилище для вашего массива в стеке с фиксированным массивом, и вы несете ответственность за резервирование хранилища для него посредством выделения.
Доступ к элементам будет точно таким же, потому что при доступе массив преобразуется в указатель на первый элемент. См .: C11 Standard - 6.3.2.1 Другие операнды - L Значения, массивы и обозначения функций (p3) В любом случае доступ к памяти осуществляется через указатель на первый элемент. При динамическом размещении вы назначаете адрес первого элемента указателю, а не компилятору, резервирующему хранилище для массива. Будь то массив с зарезервированным для вас хранилищем, или вы объявляете указатель и назначаете ему выделенный блок памяти - способ доступа к элементам будет идентичным. (пауза сделана)
Когда вы распределяете, вы можете подтвердить , что распределение прошло успешно. Таким образом, вы должны следовать своему распределению:
if (!words) { /* valdiate every allocation */
perror ("calloc-words");
exit (EXIT_FAILURE);
}
Вы уже отслеживаете index
, сообщая, сколько структур у вас есть заполнено , вам просто нужно добавить еще одну переменную, чтобы отследить, сколько структур доступно (size_t max_words = MAX_WORDS;
дает вам 2-ую переменную, установленную на начальный размер выделения MAX_WORDS
). Итак, ваш тест для «Нужно ли вам realloc
сейчас?» - это просто когда заполнено == доступно или в вашем случае if (index == max_words)
.
Поскольку теперь у вас есть возможность realloc
, ваш цикл чтения больше не должен защищать границы вашего массива, и вы можете просто читать каждое слово в файле, например,
while (fscanf (fptr, "%s", word) == 1) { /* while valid word read */
int iscap = 0, isunique = 1; /* is captial, is unique flags */
...
Теперь все, что осталось, это index == max_words
тест до того, как заполнить другой элемент. Вы можете поместить тест и realloc
перед блоками for
и if
для обработки isunique
, что нормально, или вы можете фактически поместить его в блок if (isunique)
, поскольку технически, если вы не добавляете уникальный словом, realloc
не потребуется. Единственное отличие, которое он делает, это угловой случай, когда index == max_words
и вы звоните realloc
перед циклом for
, где последнее слово не является уникальным, вы можете сделать один вызов realloc
, где это не было технически требуется (продумайте это).
Чтобы предотвратить это realloc
слишком много, поместите тест и realloc
непосредственно перед заполнением нового элемента, например,
if (isunique) { /* if unique, add to array, increment index */
if (index == max_words) { /* is realloc needed? */
/* always use a temporary pointer with realloc */
void *tmp = realloc (words, 2 * max_words * sizeof *words);
if (!tmp) {
perror ("realloc-words");
break; /* don't exit, original data still valid */
}
words = tmp; /* assign reallocated block to words */
/* (optional) set all new memory to zero */
memset (words + max_words, 0, max_words * sizeof *words);
max_words *= 2; /* update max_words to reflect new limit */
}
memcpy (words[index].word, word, len + 1); /* have len */
if (iscap) /* if cap flag set */
words[index].cap = iscap; /* set capital flag in struct */
words[index++].count++; /* increment count & index */
}
Теперь давайте подробнее рассмотрим само перераспределение, например,
if (index == max_words) { /* is realloc needed? */
/* always use a temporary pointer with realloc */
void *tmp = realloc (words, 2 * max_words * sizeof *words);
if (!tmp) { /* validate every allocation */
perror ("realloc-words");
break; /* don't exit, original data still valid */
}
words = tmp; /* assign reallocated block to words */
/* (optional) set all new memory to zero */
memset (words + max_words, 0, max_words * sizeof *words);
max_words *= 2; /* update max_words to reflect new limit */
}
Сам realloc
вызов void *tmp = realloc (words, 2 * max_words * sizeof *words);
.Почему не просто words = realloc (words, 2 * max_words * sizeof *words);
?Ответ: Вы Никогда realloc
не сами указатель, а всегда используете временный указатель.Зачем?realloc
выделяет новое хранилище, копирует существующие данные в новое хранилище и затем вызывает free()
в старом блоке памяти.При сбое (не If) realloc
возвращается NULL
и не затрагивает старый блок памяти.Если вы вслепую присваиваете NULL
своему выходному указателю words
, вы просто перезаписали адрес в свой старый блок памяти с помощью NULL
, создав утечку памяти, потому что у вас больше нет ссылки на старый блок памяти иэто не может быть освобождено.Итак, урок, Всегда realloc
с временным указателем!
Если realloc
успешно, что тогда?Обратите особое внимание на строки:
words = tmp; /* assign reallocated block to words */
/* (optional) set all new memory to zero */
memset (words + max_words, 0, max_words * sizeof *words);
max_words *= 2; /* update max_words to reflect new limit */
Первый просто назначает адрес для нового блока памяти, созданного и заполненного realloc
указателю words
.(`words теперь указывает на блок памяти с вдвое большим количеством элементов, чем раньше).
Вторая строка - напомним, realloc
и malloc
не инициализируют новую память нулем, если вы хотите инициализировать ноль памяти (что для ваших .cap
и .count
членовдействительно полезно, вы должны сделать это самостоятельно с помощью memset
. Итак, что нужно установить на ноль? Вся память, которой не было в вашем исходном блоке. Где это? Ну, это начинается с words + max_words
. Какмного нулей мне нужно написать? Вы должны заполнить всю память выше words + max_words
до конца блока. Так как вы удвоили размер, вы просто должны обнулить первоначальный размер, начиная с words + max_words
, то есть max_words * sizeof *words
байт памяти. (помните, что мы использовали 2 * max_words * sizeof *words
в качестве нового размера, и у нас НЕ обновлено max_words
, поэтому он по-прежнему содержит исходный размер)
Наконец, теперьпришло время обновить max_words
. Здесь просто сделайте так, чтобы оно совпадало с тем, что вы добавили к своему выделению в realloc
выше. Я просто удваивал размер текущего выделения каждый раз, когда вызывается realloc
, так что обновлять max_words
доновый размер размещения, вы упрощаетеу умножить на 2
с max_words *= 2;
.Вы можете добавлять столько памяти, сколько хотите каждый раз.Вы можете масштабировать до 3/2.
, вы можете добавить фиксированное количество элементов (скажем, 10
), это полностью ваше дело, но избегайте вызова realloc
, чтобы добавить 1-элемент каждый раз.Вы можете сделать это, но выделение и перераспределение являются относительно дорогостоящими операциями, поэтому лучше добавлять блок разумного размера каждый раз, когда вы realloc
, и удвоение - это разумный баланс между ростом памяти и количеством вызовов realloc
.
В целом, вы можете сделать:
/**
* C program to count occurrences of all words in a file.
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <limits.h>
#define MAX_WORD 50 /* max word size */
#define MAX_WORDS 8 /* initial number of struct to allocate */
#ifndef PATH_MAX
#define PATH_MAX 2048 /* max path (defined for Linux in limits.h) */
#endif
typedef struct { /* use a struct to hold */
char word[MAX_WORD]; /* lowercase word, and */
int cap, count; /* if it appeast capitalized, and its count */
} words_t;
char *strlwr (char *str) /* no need for unsigned char */
{
char *p = str;
while (*p) {
*p = tolower(*p);
p++;
}
return str;
}
int main (void) {
FILE *fptr;
char path[PATH_MAX], word[MAX_WORD];
size_t i, len, index = 0, max_words = MAX_WORDS;
/* pointer to allocated block of max_words struct initialized zero */
words_t *words = calloc (max_words, sizeof *words);
if (!words) { /* valdiate every allocation */
perror ("calloc-words");
exit (EXIT_FAILURE);
}
/* Input file path */
printf ("Enter file path: ");
if (scanf ("%s", path) != 1) { /* validate every input */
fputs ("error: invalid file path or cancellation.\n", stderr);
return 1;
}
fptr = fopen (path, "r"); /* open file */
if (fptr == NULL) { /* validate file open */
fputs ( "Unable to open file.\n"
"Please check you have read privileges.\n", stderr);
exit (EXIT_FAILURE);
}
while (fscanf (fptr, "%s", word) == 1) { /* while valid word read */
int iscap = 0, isunique = 1; /* is captial, is unique flags */
if (isupper (*word)) /* is the word uppercase */
iscap = 1;
/* remove all trailing punctuation characters */
len = strlen (word); /* get length */
while (len && ispunct(word[len - 1])) /* only if len > 0 */
word[--len] = 0;
strlwr (word); /* convert word to lowercase */
/* check if word exits in list of all distinct words */
for (i = 0; i < index; i++) {
if (strcmp(words[i].word, word) == 0) {
isunique = 0; /* set unique flag zero */
if (iscap) /* if capital flag set */
words[i].cap = iscap; /* set capital flag in struct */
words[i].count++; /* increment word count */
break; /* bail - done */
}
}
if (isunique) { /* if unique, add to array, increment index */
if (index == max_words) { /* is realloc needed? */
/* always use a temporary pointer with realloc */
void *tmp = realloc (words, 2 * max_words * sizeof *words);
if (!tmp) { /* validate every allocation */
perror ("realloc-words");
break; /* don't exit, original data still valid */
}
words = tmp; /* assign reallocated block to words */
/* (optional) set all new memory to zero */
memset (words + max_words, 0, max_words * sizeof *words);
max_words *= 2; /* update max_words to reflect new limit */
}
memcpy (words[index].word, word, len + 1); /* have len */
if (iscap) /* if cap flag set */
words[index].cap = iscap; /* set capital flag in struct */
words[index++].count++; /* increment count & index */
}
}
fclose (fptr); /* close file */
/*
* Print occurrences of all words in file.
*/
puts ("\nOccurrences of all distinct words with Cap in file:");
for (i = 0; i < index; i++) {
if (words[i].cap) {
strcpy (word, words[i].word);
*word = toupper (*word);
/*
* %-15s prints string in 15 character width.
* - is used to print string left align inside
* 15 character width space.
*/
printf("%-15s %d\n", word, words[i].count);
}
}
free (words);
return 0;
}
Пример использования / Вывод
Где с вашими данными выборки вы получите:
$ ./bin/unique_words_with_cap_dyn
Enter file path: dat/girljumped.txt
Occurrences of all distinct words with Cap in file:
Any 7
One 4
Some 10
The 6
A 13
Использование памяти / проверка ошибок
В любом написанном вами коде, который динамически распределяет память, у вас есть 2 обязанностей относительно любого выделенного блока памяти:(1) всегда сохраняет указатель на начальный адрес для блока памяти, поэтому (2) он может быть освобожден , когда он больше не нужен.
Крайне важно, чтобы вы использовали программу проверки ошибок памяти, чтобы гарантировать, что вы не пытаетесь получить доступ к памяти или писать за пределами / за пределами выделенного блока, пытаться прочитать или основать условный переход на неинициализированноми, наконец, подтвердить, что вы освобождаете всю выделенную память.
Для Linux valgrind
- нормальный выбор.Для каждой платформы есть похожие проверки памяти.Все они просты в использовании, просто запустите вашу программу через него.
$ valgrind ./bin/unique_words_with_cap_dyn
==7962== Memcheck, a memory error detector
==7962== Copyright (C) 2002-2015, and GNU GPL'd, by Julian Seward et al.
==7962== Using Valgrind-3.12.0 and LibVEX; rerun with -h for copyright info
==7962== Command: ./bin/unique_words_with_cap_dyn
==7962==
Enter file path: dat/girljumped.txt
Occurrences of all distinct words with Cap in file:
Any 7
One 4
Some 10
The 6
A 13
==7962==
==7962== HEAP SUMMARY:
==7962== in use at exit: 0 bytes in 0 blocks
==7962== total heap usage: 4 allocs, 4 frees, 3,912 bytes allocated
==7962==
==7962== All heap blocks were freed -- no leaks are possible
==7962==
==7962== For counts of detected and suppressed errors, rerun with: -v
Выше вы можете видеть 4
выделений и 4
освобождений (исходное выделение 8
, realloc
в8, 16 & 32
) и вы видите, что были 0
ошибки.
Всегда подтверждайте, что вы освободили всю выделенную память и что ошибок памяти нет.
Просмотрите все идайте мне знать, если у вас есть какие-либо вопросы.