Чередование обращений к процедурам New и Dispose обычно приводит к “ячеистой” структуре памяти. Все операции с кучей выполняются под управлением особой программы, которая называется администратором кучи. Она ведет учет всех свободных фрагментов в куче. При очередном обращении к процедуре New администратор кучи отыскивает наименьший свободный фрагмент, в котором еще может разместиться требуемая переменная. Адрес начала найденного фрагмента возвращается в указателе, а сам фрагмент или его часть нужной длины помечается как занятая часть кучи.
Освободить фрагмент кучи можно при использовании процедур Mark и Release (рис. 5). Процедура Mark (var p: pointer) запоминает состояние динамической памяти в тот момент, когда эта процедура вызывается. В указателе р сохраняется адрес первого байта свободной области памяти. Далее можно несколько раз выделить память. Затем процедура Release (var p: pointer) возвращает динамическую память в состояние, которое было запомнено ранее при помощи процедуры Mark.
Рис. 5. Процедуры Mark и Release
Иногда нежелательно выделять память тем способом, как это делает New. Может потребоваться выделить больше или меньше памяти, чем это делает New по умолчанию. Параметром процедуры New может быть только типизированный указатель. Для работы с нетипизированными указателями используются процедуры Getmem и Freemem:
Getmem (p, Size) – резервирование памяти;
Freemem (p, Size) – освобождение памяти,
где р – переменная типа указатель; Size – размер в байтах требуемой или освобождаемой части кучи.
Процедуры Getmem и Freemem используются в паре так же, как New и Dispose.
Существуют еще две функции, возвращающие важную информацию о динамически распределяемой области памяти: MemAvail и MaxAvail. Функция MemAvail возвращает общее число байт, доступных для распределения в динамической памяти. Перед выделением большого объема в динамически распределяемой памяти полезно убедиться, что такой объем памяти доступен. Функция MaxAvail возвращает размер наибольшего доступного блока непрерывной памяти в динамически распределяемой области. Первоначально при запуске программы MaxAvail равно MemAvail, поскольку вся динамически распределяемая область памяти является доступной и непрерывной. После распределения нескольких блоков памяти пространство в динамически распределяемой области скорее всего станет фрагментированным. Это означает, что между частями свободного пространства имеются распределенные блоки. Функция MaxAvail возвращает размер наибольшего свободного блока.
При использовании динамически распределяемых переменных часто возникает общая проблема, называемая утечкой динамической памяти. Общей причиной утечек памяти является переприсваивание динамических переменных без освобождения предыдущих. Простейшим случаем является следующий:
Var IP: ^Integer;
Begin
New(IP);
New(IP);
End.
При первом вызове New в динамически распределяемой памяти выделяется 2 байта и на них устанавливается указатель IP. Второй вызов New выделяет другие 2 байта и IP устанавливается на них. В программе первые 2 байта будут потеряны.
Естественно, утечка памяти может быть не такой очевидной. Выделение памяти почти никогда не происходит в последовательных операторах, но может выполняться в отдельных процедурах или далеко отстоящих друг от друга частях программы. В любом случае лучший способ отслеживания динамических переменных – это установка их в Nil при освобождении. При попытке распределить память нужно убедиться, что они имеют значение Nil:
Var IP: ^Integer;
Begin
New(IP);
...
Dispose(IP);
IP:= Nil;
...
If IP = Nil then New(IP);
End.