При увеличении размера блока памяти realloc()
realloc()
часто обновляет внутреннюю учетную информацию и возвращает тот же указатель. Это избавляет от необходимости копировать первоначальные данные. Однако, если это случится, В любом случае вы можете предположить, что если realloc()
NULL
, старые данные были скопированы для вас в новый участок памяти. Более того, старый указатель больше недействителен, как если бы вы вызвали free()
с ним, и использовать его больше не следует. Это верно для всех указателей на этот блок данных, а не только для того, который использовался при вызове free()
.Возможно, вы заметили, что в нашем примере для указания на измененный блок памяти использовалась отдельная переменная. Можно было бы (хотя это плохая идея) использовать ту же самую переменную, как здесь:
coordinates = realloc(coordinates, new_amount);
Это плохо по следующей причине. Когда realloc()
NULL
, первоначальный указатель все еще действителен; можно безопасно продолжить использовать эту память. Но если вы повторно используете ту же самую переменную и realloc()
возвращает NULL
, вы Для версии realloc()
ptr
равен NULL
, realloc()
действует подобно malloc()
и выделяет свежий блок памяти. Когда аргумент size
равен 0, realloc()
действует подобно free()
и ptr
. Поскольку (а) это может сбивать с толку и (б) более старые системы не реализуют эту возможность, мы рекомендуем использовать malloc()
, когда вы имеете в виду malloc()
, и free()
, когда вы имеете в виду free()
.Вот другой довольно тонкий момент[42]
. Рассмотрим процедуру, которая содержит статический указатель на динамически выделяемые данные, которые время от времени должны расти. Процедура может содержать также автоматические (т.е. локальные) указатели на эти данные. (Для краткости, мы опустим проверки ошибок. В коде продукта не делайте этого.) Например:void manage_table(void) {
static struct table *table;
struct table *cur, *p;
int i;
size_t count;
...
table =
(struct table*)malloc(count * sizeof(struct table));
/* заполнить таблицу */
cur = &table[i]; /* указатель на 1-й элемент */
...
cur->i = j; /* использование указателя */
...
if (/* некоторое условие */) {
/* нужно увеличить таблицу */
count += count/2;
p =
(struct table*)realloc(table, count * sizeof(struct table));
table = p;
}
cur->i = j; /* ПРОБЛЕМА 1: обновление элемента таблицы */
other_routine(); /* ПРОБЛЕМА 2: см. текст */
cur->j = k; /* ПРОБЛЕМА 2: см. текст */
...
}
Это выглядит просто; manage_table()
В строке, помеченной 'ПРОБЛЕМА 1
cur
был инициализирован начальным значением table
. Если некоторое условие верно и realloc()
вернула другой блок памяти, cur
теперь указывает на первоначальный, освобожденный участок памяти! Каждый раз, когда table
меняется, нужно обновить также все указатели на этот участок памяти. Здесь после вызова realloc()
и переназначения table
недостает строки 'cur = &table[i];
'.Две строки, помеченные 'ПРОБЛЕМА 2
other_routine()
делает рекурсивный вызов manage_table()
. Переменная table
снова может быть изменена совершенно незаметно! После возвращения из other_routine()
значение cur может снова стать недействительным.