Почему я получаю realloc(): неверный следующий размер?

Я пытаюсь добавить элементы в динамический массив структур в функции. Все вроде бы правильно, и когда я добавляю первый элемент, это действительно удается, но когда я пытаюсь добавить еще один, я получаю:

realloc(): invalid next size

Пробовал отлаживать разными способами, проверял, все ли значения правильные - текущий размер, выделенная память и так далее и все как положено. Я даже сделал это «вручную» (без функции), и это сработало, как и должно работать, но когда я делаю это с помощью функции, она терпит неудачу. Есть ли ошибка, которую я сделал в своем коде, которую я не вижу, или я что-то упустил в распределении динамической памяти?

Вот код:

typedef struct Product_t {
    char name[100];
    int price;
    int id;
} Product;

void add_product(Product** products, Product product, int *size) {
    (*size) += 1;
    
    if (realloc((*products), (*size) * sizeof(Product)) == NULL)
        exit(1);
        
    (*products)[*size - 1] = product;
}

int main() {

    Product* products = (Product*) malloc(0);
    int products_size = 0;
    
    Product p1 = {"product1", 45, 1};
    Product p2 = {"product2", 212, 2};
    Product p3 = {"product3", 123, 3};
    
    add_product(&products, p1, &products_size);
    add_product(&products, p2, &products_size);
    add_product(&products, p3, &products_size);
    
    return 0;
}

P.S. Я знаю, что было бы лучше сделать это, например, со связанным списком, но мне нужно сделать это с динамическим массивом.

Вы выбрасываете результат realloc. Вам нужно присвоить его переменной и (если это не NULL) присвоить его *products. (Проблема в том, что realloc может перемещать выделенную память, поэтому вам нужно обновить указатель, чтобы он указывал на новое место; старое место больше не является выделенной памятью.)

psmears 23.05.2023 16:04

Правильное использование realloc см. здесь: stackoverflow.com/questions/21006707/proper-usage-of-realloc

nielsen 23.05.2023 16:09

После вызова realloc исходное значение указателя больше не является значением, и вы отбрасываете новое значение указателя, поэтому в этот момент вы потеряны. Вам нужно сохранить указатель, возвращенный realloc, и использовать его в качестве нового адреса вашего массива. Если подумать, должно быть очевидно, почему это должно работать именно так.

Tom Karzes 23.05.2023 16:12

В основном: if (realloc((*products), (*size) * sizeof(Product)) == NULL) -> if ((*products = realloc((*products), (*size) * sizeof(Product))) == NULL)

Jabberwocky 23.05.2023 16:18

Если он вам доступен, используйте Valgrind для диагностики проблем. Если вы используете GCC, рассмотрите возможность использования средства очистки адресов. Любой или оба помогут вам обнаружить, где вы ступаете за пределы вашей памяти.

Jonathan Leffler 23.05.2023 16:31
malloc(0); ломкая и плохо очерченная. Вместо этого просто инициализируйте указатель в NULL, после чего вы можете безопасно передать его в realloc.
Lundin 23.05.2023 16:33

@Lundin: определено либо возвращать NULL, либо уникальный указатель, который можно передать free(). Любой из вариантов в данном случае работает нормально.

psmears 23.05.2023 17:02

@psmears Никаких гарантий. «или поведение такое, как если бы размер был некоторым ненулевым значением, за исключением того, что возвращаемый указатель не должен использоваться для доступа к объекту». Это все, что он говорит. Компиляторы никогда не реализовывали это последовательно или переносимо. Суть в следующем: не спешите писать странный код просто так.

Lundin 23.05.2023 18:33

Нет, есть конкретные гарантии, о которых я говорил. Да, вы не можете писать через указатель, но этот код ничего не делает и ничего не предполагает о NULL-несовности указателя (что является другой областью небезопасности).

psmears 23.05.2023 18:54

@TomKarzes @ nielsen @ psmears @ Jabberwocky Спасибо, это сработало!

gluhtuten 23.05.2023 20:02

@JonathanLeffler Я запустил Valgrind, но не нашел вывод полезным, я уже предположил, что что-то не так с realloc(), может быть, я не знаю, как его правильно использовать, поэтому это помогает мне найти, что не так.

gluhtuten 23.05.2023 20:05

Ключевым моментом Valgrind является компиляция с включенной отладкой (если вы используете GCC или Clang, убедитесь, что вы включили -g как при создании объектных файлов, так и при компоновке программы). Затем вам сообщат функцию, исходный файл, номер строки, в которой возникает проблема, и трассировку стека функций, ведущих к проблемному месту. Опустите отладочную информацию, и вы получите очень мало полезной информации.

Jonathan Leffler 23.05.2023 20:57
Стоит ли изучать 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
12
111
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

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

Адрес, указанный указателем, переданным в realloc, не обязательно останется прежним, согласно справочной странице realloc. Поэтому вам всегда нужно присваивать возвращаемое значение realloc(...)(*products), если только это не NULL.

Вообще, я не думаю, что это хорошая идея размещать ссылки на документацию по платформе, если вопрос не относится к конкретной платформе. Причина, по которой я считаю это плохой идеей, заключается в том, что конкретная платформа может предоставлять дополнительные гарантии, которых нет в стандарте ISO C. Поэтому размещение ссылок на документацию по Linux может ввести в заблуждение людей, использующих другую платформу. Для независимой от платформы документации я рекомендую эту ссылку.

Andreas Wenzel 23.05.2023 17:46

Спасибо за предупреждение, я буду осторожен @AndreasWenzel

no more sigsegv 23.05.2023 18:28

Спасибо, так сработало. Мне кажется, я где-то видел, что его можно использовать realloc(), хотя я думаю, что это не так. Я действительно забыл, что это может изменить адрес, указанный указателем.

gluhtuten 23.05.2023 19:20

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