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