|
Администратор кучи
Как уже отмечалось, администратор
кучи - это служебная подпрограмма, которая обеспечивает
взаимодействие пользовательской программы с кучей. Администратор
кучи обрабатывает запросы процедур 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 необходимо
освободить память нужного размера.
|