table =
(struct table*)malloc(count * sizeof(struct table));
...
/* заполнить таблицу */
...
table[i].i = j; /* Обновить член i-го элемента */
...
if (/* некоторое условие */) {
/* нужно увеличить таблицу */
count += count/2;
p =
(struct table*)realloc(table, count * sizeof(struct table));
table = p;
}
table[i].i = j; /* ПРОБЛЕМА 1 устраняется */
other_routine();
/* Рекурсивный вызов, модифицирует таблицу */
table[i].j = k; /* ПРОБЛЕМА 2 также устраняется */
Использование индексирования не решает проблему, если вы используете глобальную копию первоначального указателя на выделенные данные; в этом случае, вам все равно нужно побеспокоиться об обновлении своих глобальных структур после вызова
realloc()
.
ЗАМЕЧАНИЕ. Как и в случае с
malloc()
, когда вы увеличиваете размер памяти, вновь выделенная после
realloc()
память не инициализируется нулями. Вы сами при необходимости должны очистить память с помощью
memset()
, поскольку
realloc()
лишь выделяет новую память и больше ничего не делает.
3.2.1.5. Выделение с инициализацией нулями:
calloc()
Функция
calloc()
является простой оболочкой вокруг
malloc()
. Главным ее преимуществом является то, что она обнуляет динамически выделенную память. Она также вычисляет за вас размер памяти, принимая в качестве параметра число элементов и размер каждого элемента:
coordinates = (struct coord*)calloc(count, sizeof(struct coord));
По крайней мере идейно, код
calloc()
довольно простой. Вот одна из возможных реализаций:
void *calloc(size_t nmemb, size_t size) {
void *p;
size_t total;
total = nmemb * size; /* Вычислить размер */
p = malloc(total); /* Выделить память */
if (p != NULL) /* Если это сработало - */
memset(p, '\0', total); /* Заполнить ее нулями */
return p; /* Возвращаемое значение NULL или указатель */
}
Многие опытные программисты предпочитают использовать
calloc()
, поскольку в этом случае никогда не возникает вопросов по поводу вновь выделенной памяти.
Если вы знаете, что вам понадобится инициализированная нулями память, следует также использовать
calloc()
, поскольку возможно, что память, возвращенная
malloc()
, уже заполнена нулями. Хотя вы, программист, не можете этого знать,
calloc()
может это знать и избежать лишнего вызова
memset()
.
3.2.1.6. Подведение итогов из GNU Coding Standards
Чтобы подвести итоги, процитируем, что говорит об использовании процедур выделения памяти GNU Coding Standards:
Проверяйте каждый вызов
malloc
или
realloc
на предмет возвращенного нуля. Проверяйте
realloc
даже в том случае, если вы уменьшаете размер блока; в системе, которая округляет размеры блока до степени двойки,
realloc
может получить другой блок, если вы запрашиваете меньше памяти.
В Unix
realloc
может разрушить блок памяти, если она возвращает ноль. GNU
realloc
не содержит подобной ошибки: если она завершается неудачей, исходный блок остается без изменений. Считайте, что ошибка устранена. Если вы хотите запустить свою программу на Unix и хотите избежать потерь в этом случае, вы можете использовать GNU
malloc
.
Вы должны считать, что
free
изменяет содержимое освобожденного блока. Все, что вы хотите получить из блока, вы должны получать до вызова
free
.
В этих трех коротких абзацах Ричард Столмен (Richard Stallman) выразил суть важных принципов управления динамической памятью с помощью
malloc()
. Именно использование динамической памяти и принцип «никаких произвольных ограничений» делают программы GNU такими устойчивыми и более работоспособными по сравнению с их Unix-двойниками.
Мы хотим подчеркнуть, что стандарт С требует, чтобы
realloc()
не разрушал оригинальный блок памяти, если она возвращает
NULL
.
3.2.1.7. Использование персональных программ распределения
Набор функций с
malloc()
является набором общего назначения по выделению памяти. Он должен быть способен обработать запросы на произвольно большие или маленькие размеры памяти и осуществлять все необходимые учетные действия при освобождении различных участков выделенной памяти. Если ваша программа выделяет значительную динамическую память, вы можете обнаружить, что она тратит большую часть своего времени в функциях
malloc()
.
Вы можете написать персональную программу распределения — набор функций или макросов, которые выделяют большие участки памяти с помощью
malloc()
, а затем дробят их на маленькие кусочки по одному за раз. Эта методика особенно полезна, если вы выделяете множество отдельных экземпляров одной и той же сравнительно небольшой структуры.
Например, GNU awk (gawk) использует эту методику. Выдержка из файла
awk.h
в дистрибутиве
gawk
(слегка отредактировано, чтобы уместилось на странице):
#define getnode(n) if (nextfree) n = nextfree, \
nextfree = nextfree->nextp; else n = more_nodes()
#define freenode(n) ((n)->flags = 0, (n)->exec_count = 0,\
(n)->nextp = nextfree, nextfree = (n))
Переменная
nextfree
указывает на связанный список структур NODE. Макрос
getnode()
убирает из списка первую структуру, если она там есть. В противном случае она вызывает
more_nodes()
, чтобы выделить новый список свободных структур
NODE
. Макрос
freenode()
освобождает структуру
NODE
, помещая его в начало списка.