Linux программирование в примерах
Как и в случае с
, шаги стереотипны по природе и сходны по идее.malloc()1. Вычислить новый выделяемый размер в байтах.
2. Вызвать
с оригинальным указателем, полученным отrealloc()(или отmalloc()или предыдущего вызоваcalloc()) и с новым размером.realloc()3. Привести тип и присвоить возвращенное
значение. Подробнее обсудим дальше.realloc()4. Как и для
, проверить возвращенное значение, чтобы убедиться, что оно не равно NULL. Вызов любой функции выделения памяти может завершиться неудачей.malloc()При увеличении размера блока памяти
часто выделяет новый блок нужного размера, копирует данные из старого блока в новый и возвращает указатель уже на новый блок. При сокращении размера блока данныхrealloc()часто обновляет внутреннюю учетную информацию и возвращает тот же указатель. Это избавляет от необходимости копировать первоначальные данные. Однако, если это случится, не думайте, что можно использовать память за пределами нового размера!realloc()В любом случае вы можете предположить, что если
не возвращаетrealloc(), старые данные были скопированы для вас в новый участок памяти. Более того, старый указатель больше недействителен, как если бы вы вызвалиNULLс ним, и использовать его больше не следует. Это верно для всех указателей на этот блок данных, а не только для того, который использовался при вызовеfree().free()Возможно, вы заметили, что в нашем примере для указания на измененный блок памяти использовалась отдельная переменная. Можно было бы (хотя это плохая идея) использовать ту же самую переменную, как здесь:
coordinates = realloc(coordinates, new_amount);Это плохо по следующей причине. Когда
возвращаетrealloc(), первоначальный указатель все еще действителен; можно безопасно продолжить использовать эту память. Но если вы повторно используете ту же самую переменную иNULLвозвращаетrealloc(), вы теряете указатель на первоначальную память. Эту память больше нельзя использовать. Что еще важнее, эту память невозможно освободить! Это создает утечку памяти, которую нужно избежать.NULLДля версии
в стандартном С есть некоторые особые случаи: когда аргументrealloc()равенptr,NULLдействует подобноrealloc()и выделяет свежий блок памяти. Когда аргументmalloc()равен 0,sizeдействует подобно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()В строке, помеченной '
', указатель cur используется для обновления элемента таблицы. Однако,ПРОБЛЕМА 1был инициализирован начальным значениемcur. Если некоторое условие верно иtableвернула другой блок памяти,realloc()теперь указывает на первоначальный, освобожденный участок памяти! Каждый раз, когдаcurменяется, нужно обновить также все указатели на этот участок памяти. Здесь после вызоваtableи переназначенияrealloc()недостает строки 'table'.cur = &table[i];Две строки, помеченные '
', еще более тонкие. В частности, предположим, чтоПРОБЛЕМА 2делает рекурсивный вызовother_routine(). Переменнаяmanage_table()снова может быть изменена совершенно незаметно! После возвращения изtableзначение cur может снова стать недействительным.other_routine()Можно подумать (что мы вначале и сделали), что единственным решением является знать это и добавить после вызова функции переназначение
с соответствующим комментарием. Однако, Брайан Керниган (Brian Kernighan) любезно нас поправил. Если мы используем индексирование, проблема поддержки указателя даже не возникает:cur