Администратор кучи
Как уже отмечалось, администратор кучи - это служебная подпрограмма, которая обеспечивает взаимодействие пользовательской программы с кучей. Администратор кучи обрабатывает запросы процедур NEW, GETMEM, DISPOSE, FREEMEM и др. и изменяет значения указателей HEAPPTR и FREELIST. Указатель HEAPPTR содержит адрес нижней границы свободной части кучи, а указатель FREELIST - адрес описателя первого свободного блока. В модуле SYSTEM указатель FREELIST описан как POINTER, однако фактически он указывает на следующую структуру данных:
type
PFreeRec = ATFreeRec;
TFreeRec = record
Next : pointer;
Size : pointer
end;
Эта списочная структура предназначена для описания всех свободных блоков памяти, которые расположены ниже границы HEAPPTR. Происхождение блоков связано со случайной последовательностью использования процедур NEW-DISPOSE или GETMEM-FREEMEM («ячеистая» структура кучи). Поле NEXT, в записи TFREEREC содержит адрес описателя следующего по списку свободного блока кучи или адрес, совпадающий с HEAPEND, если этот участок последний в списке. Поле SIZE содержит ненормализованную длину свободного блока или 0, если ниже адреса, содержащегося в HEAPPTR, нет свободных блоков. Ненормализованная длина определяется так: в старшем слове этого поля содержится количество свободных параграфов, а в младшем - количество свободных байт в диапазоне 0... 15. Следующая функция преобразует значение поля SIZE в фактическую длину свободного блока:
Function BlbckSize(Size: pointer): Longint;
{Функция преобразует ненормализованную длину свободного блока в байты}
type
PtrRec = record
Lo, Hi : word
end;
var
LengthBlock: Longint;
begin
BlockSize := Longint(PtrRec(Size).Hi)*16 + PtrRec(Size).Lo
end;
Сразу после загрузки программы указатели HEAPPTR и FREELIST содержат один и тот же адрес, который совпадает с началом кучи (этот адрес содержится в указателе HEAPORG). При этом в первых 8 байтах кучи хранится запись, соответствующая типу TFREEREC (поле NEXT содержит адрес, совпадающий со значением HEAPEND, a поле SIZE - ноль, что служит дополнительным признаком отсутствия «ячеек» в динамической памяти). При работе с кучей указатели HEAPPTR и FREELIST будут иметь одинаковые значения до тех пор, пока в куче не образуется хотя бы один свободный блок ниже границы, содержащейся в указателе HEAPPTR. Как только это произойдет, указатель FREELIST станет ссылаться на начало этого блока, а в первых 8 байтах освобожденного участка памяти будет размещена запись TFREEREC. Используя FREELIST как начало списка, программа пользователя всегда сможет просмотреть весь список свободных блоков и при необходимости модифицировать его.
Описанный механизм вскрывает один не очень существенный недостаток, связанный с работой администратора кучи, а именно: в любой освободившийся блок администратор должен поместить описатель этого блока, а это означает, что длина блока не может быть меньше 8 байтов. Администратор кучи всегда выделяет память блоками, размер которых кратен размеру записи TFREEREC, т.е. кратен 8 байтам. Даже если программа запросит 1 байт, администратор выделит ей фактически 8 байт. Те же 8 байт будут выделены при запросе 2, 3 ,..., 8 байт; при запросе 9 байт будет выделен блок в 16 байт и т.д. Это обстоятельство следует учитывать, если Вы хотите минимизировать возможные потери динамической памяти. Если запрашиваемый размер не кратен 8 байтам, в куче образуется <дырка> размером от 1 до 7 байт, причем она не может использоваться ни при каком другом запросе динамической памяти вплоть до того момента, когда связанная с ней переменная не будет удалена из кучи.
Если при очередном обращении к функции NEW или GETMEM администратор не может найти в куче нужный свободный блок, он обращается к функции, адрес которой содержит переменная HEAPERROR. Эта функция соответствует следующему процедурному типу:
type
HeapErrorFun = function (Size:word): Integer;
Здесь SIZE - размер той переменной, для которой нет свободной динамической памяти. Стандартная функция, адрес которой при запуске программы содержит переменная HEAPERROR, возвращает 0, что приводит к останову программы по ошибке периода счета с кодом 203 (см. прил. 3). Вы можете переопределить эту функцию и таким образом блокировать останов программы. Для этого необходимо написать собственную функцию и поместить ее адрес в указатель HEAPERROR. Например:
Function HeapFunc(Size: Word): Integer; far;
begin
HeapFunc := 1 end;
begin {Основная программа}
HeapError := @HeapFunc;
.......
end.
Отметим, что функция типа HEAPERRORFUN вызывается только в том случае, когда обращение с требованием выделения динамической памяти было неуспешным. Она может возвращать одно из трех значений:
0 - прекратить работу программы;
1 - присвоить соответствующему указателю значение NIL и продолжить работу программы;
2 - повторить выделение памяти; разумеется, в этом случае внутри функции типа HEAPERRORFUN необходимо освободить память нужного размера.