Иногда необходимо знать величину еще ни разу не использованного пространства кучи (между значениями указателей FreePtr сверху и HeapPtr снизу). Функция HeapAvail, анализирующая размер именно этого пространства (без учета освобожденных блоков в куче), приводится на рис. 11.7.
| { $М 1024, 4000, 4000} (*заданные параметры кучи для теста*)
| FUNCTION HeapAvail : LongInt;
| VAR
| LongSeg, LongFreePtr, LongHeapPtr : LongInt;
| BEGIN
| LongSeg := Seg(FreePtr^)+$1000*Byte(Ofs(FreePtr^)=0);
| LongFreePtr :=(LongSeg * 16 ) + Ofs(FreePtr^);
| LongSeg := Seg( HeapPtr^);
| LongHeapPtr := ( LongSeg*16 ) + Ofs( HeapPtr^);
| HeapAvail := LongFreePtr – LongHeapPtr
| END;
| procedure WriteAvail; { вспомогательная процедура }
| begin
| WriteLn( ' MemAvail=', MemAvail :6,
| ' MaxAvail=', MaxAvail :6,
| ' HeapAvail=', HeapAvail :6 )
| end; {WriteAvail}
| VAR { ПРИМЕР АНАЛИЗА ПАМЯТИ КУЧИ }
| P1, P2 : Pointer;
| BEGIN
| WriteLn( 'Начало работы:' ); WriteAvail;
| GetMem ( P1, 1000 ); { отводится 1000 байт в куче }
| GetMem ( Р2, 10 ); { отводится 10 байт в куче }
| WriteLn('Размещены в куче 2 переменные(1000 и 10 б)');
| WriteAvail;
| FreeMem( P1, 1000 ); { освобождается первый блок }
- 208 -
{Сейчас в куче появилась дыра, а в списке свободных блоков появились ее координаты, уменьшив кучу на 8 байт.}
| WrfteLn( 'Освобождена ссылка на 1000 байт:' );
| WriteAvail;
| FreeMem( P2, 10 ); { освобожден второй блок }
| { Теперь вся куча пуста, и нет нужды в списке блоков. }
| WriteLn( 'Освобождена ссылка на 10 байт:' );
| WriteAvail;
| ReadLn { пауза до нажатия клавиши ввода }
| END.
Рис. 11.7 (окончание)
Заметьте, что все локальные параметры в функции HeapAvail имеют тип LongInt, чтобы хранить десятичные значения ненормализованных (абсолютных) адресов. Чтобы избежать потери порядка из-за превышения диапазона типа Word, значения функций Seg(...) перед умножением переписываются в переменную более «длинного» типа LongInt.
Следующий вопрос, связанный со списком свободных блоков, — это потенциальная проблема дробности. Она связана с тем, что дробность в мониторе кучи равна одному байту. Это означает, что при размещении одного байта переменная будет занимать один байт. При размещении и удалении в произвольном порядке большого числа переменных или блоков различной длины могут появляться свободные блоки небольшого размера, причем число таких блоков может быть большим. Так, например, при освобождении блока размером 20 байт и размещении вслед за этим блока размером 19 байт, появится свободный блок длиной 1 байт, который может находиться в списке свободных блоков довольно долго. Для решения этой проблемы справочное руководство по Турбо Паскалю советует воспользоваться следующим приемом. Каждый раз выделять и впоследствии освобождать блок с размером, кратным какому-либо целому числу байтов. Для этого необходимо переопределить процедуры GetMem и FreeMem. Например, если мы хотим выделять память блоками с размером, кратным 16 байт, то переопределения GetMem и FreeMem будут выглядеть следующим образом:
PROCEDURE GetMem( VAR P : Pointer; Size : Word );
BEGIN
System.GetMem(P, (Size+15) and $FFF0);
END; { GetMem }
- 209 -
PROCEDURE FreeMem( VAR P : Pointer; Size : Word );
BEGIN
System.FreeMem( P, (Size+15) and $FFF0);
END; { FreeMem }
В этом случае минимальный размер свободного блока будет не меньше 16 байт. Однако это будет справедливо до тех пор, пока не будут применены процедуры New и Dispose. Процедура New, например, может для переменной, имеющей размер, не кратный 16 байт, использовать такой свободный блок, что останется пустой излишек с размером менее 16 байт. И уж он-то будет находиться в памяти очень долго. Ситуация осложняется еще тем, что мы теперь уже не можем так просто управлять размером выделяемого блока. Возможным средством улучшить ситуацию является приведение размера динамической переменой к величине, кратной 16 байт. Так, например, если необходимо размещать в куче переменные типа:
TYPE
Dot = RECORD x,y : Real END;