Linux программирование в примерах - Роббинс Арнольд
Шрифт:
Интервал:
Закладка:
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, ' ', 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, помещая его в начало списка.