Итак, это часть моей программы, которая анализирует одну строку файла в массив num_args, который я использую для дальнейшей реализации:
while(fgets(str, 1024, f) > 0) {
int id;
int final_arg_num;
int my_index;
char *num_args[100];
for (id = 0, line = strtok(str, " "); id < 100; id++) {
if (line == NULL) {
break;
}
num_args[id] = malloc(16*sizeof(char));
sscanf(line, "%s", num_args[id]);
line = strtok(NULL, " ");
}
final_arg_num = id;
char *storage_people = (char *)malloc(sizeof(char)*need);
if (strcmp(num_args[0],"Movie:") != 0) {
strcpy(storage_people,num_args[0]);
} else {
strcpy(storage_people,"");
}
for (my_index = 1; my_index < final_arg_num; my_index++) {
if (strcmp(num_args[0],"Movie:") || (!strcmp(num_args[0],"Movie:") && my_index > 1))
strcat(storage_people, " " );
strcat(storage_people, num_args[my_index]);
}
if (strcmp(num_args[0],"Movie:") == 0) {
// do something
} else {
// do something
}
/**for (j = 0; j < 100; j++) {
if (num_args[j] != NULL) {
free(num_args[j]);
}
}**/
free(storage_people);
}
fclose(f);
Если я не освобожу num_args, я получаю утечку памяти; Если я раскомментирую часть моей программы free(num_args[j]), я получу ошибку valgrind, подобную этой:
==3062== Conditional jump or move depends on uninitialised value(s)
==3062== at 0x401D3B: main (original.c:410)
==3062==
==3062== Conditional jump or move depends on uninitialised value(s)
==3062== at 0x4C2BDA2: free (in .*)
==3062== by 0x401D54: main (original.c:411)
==3062==
==3062== Invalid free() / delete / delete[] / realloc()
==3062== at 0x4C2BDEC: free (in .*)
==3062== by 0x401D54: main (original.c:411)
==3062== Address 0x8 is not stack'd, malloc'd or (recently) free'd
Любая помощь?





Проблема в том, что
char *num_args[100];
объявляет массив указателей, но они не инициализированы. Ваш шлейф for, который
анализирует строку не обязательно устанавливает все 100 пробелов в массиве, поэтому некоторые
останутся неинициализированными и, скорее всего, != NULL.
Вот почему free терпит неудачу, потому что в какой-то момент вашего свободного цикла вы пытаетесь
сделать free(num_args[j]) для num_args[j], который не был инициализирован и
это не NULL, поэтому он вылетает.
Вы должны инициализировать массив, либо с помощью memset, как это
char *num_args[100];
memset(num_args, 0, sizeof num_args);
или с помощью списка инициализации
char *num_args[100] = { NULL };
который инициализирует весь указатель на указатель ноль1,2.
И вы должны сделать проверку fgets следующим образом
while(fgets (str , 1024 , f))
или как это
while(fgets (str , 1024 , f) != NULL)
Фотеноты
1 Как указывает chux в комментариях, мое утверждение который инициализирует весь указатель на NULL.
не совсем верно, так как только первый элемент инициализируется NULL, все остальные
элементы инициализируются 0-битным шаблоном. Могут быть архитектуры
где NULL не представлен 0-битным шаблоном, а остальная часть элемента
не будет указывать на NULL. Но в большинстве архитектур NULL - это 0-битный шаблон и
в результате все элементы будут указывать на NULL. Видеть
https://ideone.com/RYAyHm как пример этого скомпилирован с GCC.
2 Я использую формулировку, использованную во втором комментарии chux, объяснение очень хорошее.
@chux да, ты прав, я добавил сноску об этом. Но действительно ли существуют архитектуры, которые используют люди, в которых NULL не является 0-битным шаблоном?
@chux или это так, C11 говорит, что указатели инициализируются NULL (6.7.9 p10 и p20)
@AnttiHaapala C11 не говорит, что указатели инициализируются на NULL. «если он имеет тип указателя, он инициализируется нулевым указателем;». Этот эффект аналогичен функциональный в том смысле, что указатель является нулевой указатель, даже если это не NULL. Напомним, в системе может быть более одной кодировки нулевой указатель. Здесь применяется p21: снова результатом является нулевой указатель, даже если это не то же самое, что и NULL, он будет иметь ту же функциональность, хотя потенциально может другое представление. Если бы в этом ответе было сказано «который инициализирует весь указатель на нулевой указатель», было бы меньше причин для комментариев.
@chux спасибо за отзыв, я изменил формулировку для вашей (и, очевидно, добавил замечание о том, что вы сказали эти слова).
@Pablo Мой комментарий был тонким моментом в вашем прекрасном ответе. На самом деле основная проблема заключается в том, что some_type_args[100] = { some_zero_like_value }; инициализирует первый элемент как some_zero_like_value, а остальные с инициализацией по умолчанию, которые "нулевые", но могут отличаться тонким способом. Продолжайте в том же духе.
@chux: это византийское обсуждение, но char *num_args[100] = { NULL }; гораздо менее проблематичен, чем memset(num_args, 0, sizeof num_args);, который инициализирует массив всеми нулевыми битами, что не обязательно является представлением нулевого указателя. На самом деле не имеет значения, могут ли нулевые указатели иметь несколько представлений: не должны ли все представления нулевых указателей сравниваться одинаково?
Стандарт также гарантирует, что char *num_args[100] = {0}; правильно назначит все значения num_args для NULL, а memset(num_args, 0, sizeof num_args); - нет. Кстати, мне это решение не нравится, было бы гораздо лучше подсчитать количество действительных значений в массиве, а не просто инициализировать все на NULL, мы находимся в C, а не в JAVA. «мой оператор, который инициализирует весь указатель на NULL. не совсем верно, поскольку только первый элемент инициализируется значением NULL, все остальные элементы инициализируются 0-битным шаблоном.», это неверно @chux
@Stargateur Я согласен, что «все остальные элементы инициализируются 0-битным шаблоном» лучше, чем «если он имеет тип указателя, он инициализируется нулевым указателем;». Я не согласен с тем, что стандарт определяет: «char *num_args[100] = {0}; назначит все значения num_args для NULL. Он назначит последние 99 элементов некоторому нулевой указатель, будь то (void*) NULL или какой-то другой нулевой указатель. Функционально это мизерная разница.
В вашем коде несколько проблем:
основной цикл использует фиктивный тест на конец файла: вместо этого вы должны написать while (fgets(str, 1024, f) != NULL) {
Цикл для освобождения выделенного указателя должен останавливаться на id: за пределами этого индекса все указатели неинициализированы, поэтому их передача в free имеет неопределенное поведение. Также обратите внимание, что передавать нулевой указатель на free совершенно безопасно. Если вы измените цикл таким образом, нет необходимости инициализировать этот массив:
for (j = 0; j < id; j++) {
free(num_args[j]);
}
способ сохранения слов в массиве неэффективен и рискован: вы выделяете 16 байт памяти и используете sscanf() со спецификатором преобразования %s для копирования слово, проанализированного strtok.
num_args[id] с помощью sscanf(line, "%15s", num_args[id]);.\r, \n, \t ... вы можете просто сохранить слово с помощью num_args[id] = strdup(line);.strtok: line = strtok(str, " \t\r\n\v\f")storage_people также проблематично: вы не проверяете, достаточно ли байтов need места для результирующей строки, включая завершающий нулевой байт. Вы должны использовать служебную функцию для объединения двух строк с перераспределением до нужного размера.
"который инициализировал весь указатель на
NULL." -> не совсем:char *num_args[100] = { NULL };инициализирует первый элемент какNULL, а остальные 99 - как нулевой бит. МАК, все будет нулевые указатели. На ожидаемых платформахNULLне является шаблоном с нулевым битом.