Я пытаюсь добавить элементы в динамический массив структур в функции. Все вроде бы правильно, и когда я добавляю первый элемент, это действительно удается, но когда я пытаюсь добавить еще один, я получаю:
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 см. здесь: stackoverflow.com/questions/21006707/proper-usage-of-realloc
После вызова realloc исходное значение указателя больше не является значением, и вы отбрасываете новое значение указателя, поэтому в этот момент вы потеряны. Вам нужно сохранить указатель, возвращенный realloc, и использовать его в качестве нового адреса вашего массива. Если подумать, должно быть очевидно, почему это должно работать именно так.
В основном: if (realloc((*products), (*size) * sizeof(Product)) == NULL) -> if ((*products = realloc((*products), (*size) * sizeof(Product))) == NULL)
Если он вам доступен, используйте Valgrind для диагностики проблем. Если вы используете GCC, рассмотрите возможность использования средства очистки адресов. Любой или оба помогут вам обнаружить, где вы ступаете за пределы вашей памяти.
malloc(0); ломкая и плохо очерченная. Вместо этого просто инициализируйте указатель в NULL, после чего вы можете безопасно передать его в realloc.
@Lundin: определено либо возвращать NULL, либо уникальный указатель, который можно передать free(). Любой из вариантов в данном случае работает нормально.
@psmears Никаких гарантий. «или поведение такое, как если бы размер был некоторым ненулевым значением, за исключением того, что возвращаемый указатель не должен использоваться для доступа к объекту». Это все, что он говорит. Компиляторы никогда не реализовывали это последовательно или переносимо. Суть в следующем: не спешите писать странный код просто так.
Нет, есть конкретные гарантии, о которых я говорил. Да, вы не можете писать через указатель, но этот код ничего не делает и ничего не предполагает о NULL-несовности указателя (что является другой областью небезопасности).
@TomKarzes @ nielsen @ psmears @ Jabberwocky Спасибо, это сработало!
@JonathanLeffler Я запустил Valgrind, но не нашел вывод полезным, я уже предположил, что что-то не так с realloc(), может быть, я не знаю, как его правильно использовать, поэтому это помогает мне найти, что не так.
Ключевым моментом Valgrind является компиляция с включенной отладкой (если вы используете GCC или Clang, убедитесь, что вы включили -g как при создании объектных файлов, так и при компоновке программы). Затем вам сообщат функцию, исходный файл, номер строки, в которой возникает проблема, и трассировку стека функций, ведущих к проблемному месту. Опустите отладочную информацию, и вы получите очень мало полезной информации.





Адрес, указанный указателем, переданным в realloc, не обязательно останется прежним, согласно справочной странице realloc. Поэтому вам всегда нужно присваивать возвращаемое значение realloc(...)(*products), если только это не NULL.
Вообще, я не думаю, что это хорошая идея размещать ссылки на документацию по платформе, если вопрос не относится к конкретной платформе. Причина, по которой я считаю это плохой идеей, заключается в том, что конкретная платформа может предоставлять дополнительные гарантии, которых нет в стандарте ISO C. Поэтому размещение ссылок на документацию по Linux может ввести в заблуждение людей, использующих другую платформу. Для независимой от платформы документации я рекомендую эту ссылку.
Спасибо за предупреждение, я буду осторожен @AndreasWenzel
Спасибо, так сработало. Мне кажется, я где-то видел, что его можно использовать realloc(), хотя я думаю, что это не так. Я действительно забыл, что это может изменить адрес, указанный указателем.
Вы выбрасываете результат
realloc. Вам нужно присвоить его переменной и (если это неNULL) присвоить его*products. (Проблема в том, чтоreallocможет перемещать выделенную память, поэтому вам нужно обновить указатель, чтобы он указывал на новое место; старое место больше не является выделенной памятью.)