Ваша структура более сложна на один уровень динамического выделения памяти, чем обычно необходимо.У вас есть:
typedef struct a
{
unsigned long x;
...and other members...
} A, *PA;
typedef struct b
{
unsigned char length;
PA * x;
} B, *PB;
Последний член B - это struct a **
, который может понадобиться, но редко бывает.Вы, вероятно, должны упростить все, используя:
typedef struct a
{
unsigned long x;
} A;
typedef struct b
{
unsigned length;
A *array;
} B;
Это переписывание отражает личные предубеждения против typedefs для указателей (поэтому я исключил PA и PB).Я изменил тип length
в B на unsigned
с unsigned char
;использование unsigned char
экономит место в показанном дизайне, хотя это, возможно, могло бы сэкономить пространство, если бы вы отслеживали выделенную длину отдельно от используемой длины (но даже тогда я бы, вероятно, использовал unsigned short
вместо unsigned char
).
И, самое главное, он меняет тип массива, поэтому у вас нет отдельного указателя для каждого элемента, поскольку массив содержит сами элементы.Теперь, иногда, вам действительно нужно обрабатывать массивы указателей.Но это относительно необычно и определенно усложняет управление памятью.
Код в вашей функции test()
упрощает:
void init_b(unsigned char length, B *b)
{
b->length = length;
b->x = (A *)malloc(length*sizeof(*b->x));
for (int i = 0; i < length; i++)
b->x[i] = 0;
}
int main()
{
B b;
init_b(4, &b);
return 0;
}
Использование идиоматического цикла for
позволяет избежать выхода за пределы(одна из проблем в оригинальном коде).Цикл инициализации для выделенной памяти может быть заменен операцией memset()
, или вы можете использовать calloc()
вместо malloc()
.
Ваш исходный код устанавливал указатели в массиве указателей наноль;вы не могли тогда получить доступ к каким-либо данным, потому что не было никаких данных;Вы не выделили пространство для фактических struct a
значений, просто пространство для массива указателей на такие значения.
Конечно, код должен либо проверять, не удалось ли выделить память, либо использовать функцию покрытия дляРаспределитель памяти, который гарантирует, что никогда не вернется, если выделение памяти не удается.Не безопасно предполагать, что выделение памяти будет выполнено без проверки где-либо.Функции покрытия для распределителей часто идут по именам, таким как xmalloc()
или emalloc()
.
Кто-то еще указал, что malloc.h
является нестандартным.Если вы используете средства настройки, которые он предоставляет, или средства отчетности, которые он предоставляет, то malloc.h
- это хорошо (но оно не доступно везде, поэтому оно ограничивает переносимость вашего кода).Тем не менее, большинство людей в большинстве случаев должны просто забыть о malloc.h
и использовать вместо него #include <stdlib.h>
;использование malloc.h
является признаком размышлений со времен до появления стандарта C89, когда не было заголовка, объявляющего malloc()
и др., и это long время назад.
См. Также Освобождение 2D-массива стека ;код был изоморфен с этим кодом (вы в том же классе?).И я рекомендовал и проиллюстрировал то же упрощение там.