Функция free() может также использоваться для освобождения памяти, выделенной с помощью calloc().
Динамическое распределение памяти является ключевым средством во многих развитых технологиях программирования. Мы исследуем некоторые из них в главе 17. Вполне вероятно, что ваша библиотека С предлагает ряд других функций управления памятью, при этом часть из них переносима, а часть — нет. Уделите время, чтобы взглянуть на них.
Динамическое распределение памяти и массивы переменной длины
Функциональность массивов переменной длины и malloc() кое в чем пересекается. Например, оба средства могут применяться для создания массива, размер которого определяется во время выполнения:
int vlamal()
{
int n; int * pi;
scanf("%d", &n);
pi = (int *) malloc (n * sizeof(int)); int ar[n]; // массив переменной длины
pi [2] = ar [2] = -5;
} ' "
Одно из отличий между ними заключается в том, что массив переменной длины является автоматической памятью. Следствие использования автоматической памяти состоит в том, что пространство памяти, занимаемое массивом переменной длины, освобождается автоматически, когда поток управления покидает блок, в котором массив определен — в этом случае при завершении функции vlamal(). Таким образом, вы не должны переживать о вызове free(). С другой стороны, доступ к массиву, созданному с применением malloc(), не ограничивается одной функцией.
Классы хранения, связывание и управление памятью 515
Например, одна функция может создать массив и возвратить указатель, предоставляя доступ к нему вызывающей функции. Завершив работу с массивом, вызывающая функция может вызвать free(). Не будет ошибкой, если при вызове free() задать указатель, отличающийся от используемого в вызове malloc(); нужно только, чтобы указатели содержали один и тот же адрес. Однако вы не должны пытаться освободить тот же самый блок памяти дважды.
Массивы переменной длины более удобны для организации многомерных массивов. Вы можете создать двумерный массив с применением malloc(), но синтаксис будет довольно неуклюжим. Если компилятор не поддерживает средство массивов переменной длины, одна из размерностей должна быть зафиксирована, как в вызовах функции:
int n = 5; int m = 6;
int ar2[n][m]; // массив переменной длины n х m
int (* р2)[6]; // работает до выхода стандарта С99
int (* рЗ)[m]; // требуется поддержка массивов переменной длины
р2 = (int (*) [6] ) malloctn * 6 * sizeof(int)); // массив n * 6
рЗ = (int (*)[m]) malloctn * m * sizeof(int)); // массив n * m
// предыдущее выражение также требует поддержки массивов переменной длины
ar2 [1] [2] = р2[1] [2] =12;
Полезно взглянуть на объявления указателей. Функция malloc() возвращает указатель, так что р2 должен быть указателем подходящего типа. Объявление
int (* р2) [6]; // работает до выхода стандарта С99
говорит о том, что р2 указывает на массив из шести элементов int. Это значит, что р2 [i] будет интерпретироваться как элемент, состоящий из шести значений int, а р2 [i] [j] — это одиночное значение int.
Во втором объявлении указателя используется переменная для сообщения размера массива, на который ссылается рЗ. Это означает, что рЗ трактуется как указатель на массив переменной длины, и именно потому данный код не будет работать в рамках стандарта С90.
Классы хранения и динамическое распределение памяти
Вас может интересовать, какова связь между классами хранения и динамическим распределением памяти. Давайте посмотрим на идеализированную модель. Вы можете думать о доступной памяти программы как об имеющей три отдельных области: одна для статических переменных с внешним связыванием, внутренним связыванием и без связывания; одна для автоматических переменных; и одна для динамически выделяемой памяти.
Объем памяти, необходимый для классов со статической продолжительностью хранения, известен на этапе компиляции, и данные, которые хранятся в этой области, доступны на всем протяжении выполнения программы. Каждая переменная этих классов появляется, когда программа запускается, и исчезает при ее завершении.
Однако автоматическая переменная начинает существовать, когда поток управления входит в блок кода, содержащий определение переменной, и исчезает после покидания этого блока. Следовательно, по мере вызова программой функций и их завершения, объем памяти, задействованный под автоматические переменные, возрастает и уменьшается. Такая область памяти обычно реализована в виде стека. Это значит, что новые переменные добавляются в память последовательно, в порядке их создания, а удаляются в обратном порядке, когда исчезают.
Читать дальше