Невозможно правильно выполнить malloc и освободить память в моей программе strtok

Итак, это часть моей программы, которая анализирует одну строку файла в массив 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

Любая помощь?

Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
1
0
99
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Ответ принят как подходящий

Проблема в том, что

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, объяснение очень хорошее.

"который инициализировал весь указатель на NULL." -> не совсем: char *num_args[100] = { NULL }; инициализирует первый элемент как NULL, а остальные 99 - как нулевой бит. МАК, все будет нулевые указатели. На ожидаемых платформах NULL не является шаблоном с нулевым битом.

chux - Reinstate Monica 08.04.2018 05:22

@chux да, ты прав, я добавил сноску об этом. Но действительно ли существуют архитектуры, которые используют люди, в которых NULL не является 0-битным шаблоном?

Pablo 08.04.2018 05:40

@chux или это так, C11 говорит, что указатели инициализируются NULL (6.7.9 p10 и p20)

Antti Haapala 08.04.2018 06:42

@AnttiHaapala C11 не говорит, что указатели инициализируются на NULL. «если он имеет тип указателя, он инициализируется нулевым указателем;». Этот эффект аналогичен функциональный в том смысле, что указатель является нулевой указатель, даже если это не NULL. Напомним, в системе может быть более одной кодировки нулевой указатель. Здесь применяется p21: снова результатом является нулевой указатель, даже если это не то же самое, что и NULL, он будет иметь ту же функциональность, хотя потенциально может другое представление. Если бы в этом ответе было сказано «который инициализирует весь указатель на нулевой указатель», было бы меньше причин для комментариев.

chux - Reinstate Monica 08.04.2018 07:25

@chux спасибо за отзыв, я изменил формулировку для вашей (и, очевидно, добавил замечание о том, что вы сказали эти слова).

Pablo 08.04.2018 07:34

@Pablo Мой комментарий был тонким моментом в вашем прекрасном ответе. На самом деле основная проблема заключается в том, что some_type_args[100] = { some_zero_like_value }; инициализирует первый элемент как some_zero_like_value, а остальные с инициализацией по умолчанию, которые "нулевые", но могут отличаться тонким способом. Продолжайте в том же духе.

chux - Reinstate Monica 08.04.2018 07:38

@chux: это византийское обсуждение, но char *num_args[100] = { NULL }; гораздо менее проблематичен, чем memset(num_args, 0, sizeof num_args);, который инициализирует массив всеми нулевыми битами, что не обязательно является представлением нулевого указателя. На самом деле не имеет значения, могут ли нулевые указатели иметь несколько представлений: не должны ли все представления нулевых указателей сравниваться одинаково?

chqrlie 08.04.2018 08:02

Стандарт также гарантирует, что char *num_args[100] = {0}; правильно назначит все значения num_args для NULL, а memset(num_args, 0, sizeof num_args); - нет. Кстати, мне это решение не нравится, было бы гораздо лучше подсчитать количество действительных значений в массиве, а не просто инициализировать все на NULL, мы находимся в C, а не в JAVA. «мой оператор, который инициализирует весь указатель на NULL. не совсем верно, поскольку только первый элемент инициализируется значением NULL, все остальные элементы инициализируются 0-битным шаблоном.», это неверно @chux

Stargateur 08.04.2018 08:06

@Stargateur Я согласен, что «все остальные элементы инициализируются 0-битным шаблоном» лучше, чем «если он имеет тип указателя, он инициализируется нулевым указателем;». Я не согласен с тем, что стандарт определяет: «char *num_args[100] = {0}; назначит все значения num_args для NULL. Он назначит последние 99 элементов некоторому нулевой указатель, будь то (void*) NULL или какой-то другой нулевой указатель. Функционально это мизерная разница.

chux - Reinstate Monica 08.04.2018 09:24

В вашем коде несколько проблем:

  • основной цикл использует фиктивный тест на конец файла: вместо этого вы должны написать 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 места для результирующей строки, включая завершающий нулевой байт. Вы должны использовать служебную функцию для объединения двух строк с перераспределением до нужного размера.

Другие вопросы по теме