Иллюстрированный самоучитель по Turbo Pascal

Выделение и освобождение динамической памяти

В приведенном выше примере процедурой MARK(P) в указатель Р было помещено текущее значение HEAPPTR, однако память под переменную не резервировалась. Обращение RELEASE(P) освободило динамическую память от помеченного места до конца кучи. Рис. 6.4 иллюстрирует механизм работы процедур NEW-DISPOSE и NEW-MARK-RELEASE для рассмотренного примера и для случая, когда вместо оператора RELEASE(P) используется, например, DISPOSE(P3).

Следует учесть, что вызов RELEASE уничтожает список свободных фрагментов в куче, созданных до этого процедурой DISPOSE, поэтому совместное использование обоих механизмов освобождения памяти в рамках одной программы не рекомендуется.

Как уже отмечалось, параметром процедуры NEW может быть только типизированный указатель. Для работы с нетипизированными указателями используются процедуры:

GETMEM (P, SIZE) – резервирование памяти;
FREEMEM(P, SIZE) – освобождение памяти.

Здесь:

  • Р – нетипизированный указатель;
  • SIZE – размер в байтах требуемой или освобождаемой части кучи.

Иллюстрированный самоучитель по Turbo Pascal › Указатели и динамическая память › Выделение и освобождение динамической памяти
Рис. 6.4. Состояние динамической памяти: а) перед освобождением; б) после Dispose(p3); в) после Release(p)

За одно обращение к куче процедурой GETMEM можно зарезервировать до 65521 байта динамической памяти.

Использование процедур GETMEM-FREEMEM, как и вообще вся работа с динамической памятью, требует особой осторожности и тщательного соблюдения простого правила: освобождать нужно ровно столько памяти, сколько ее было зарезервировано, и именно с того адреса, с которого она была зарезервирована.

Нетрудно обнаружить, что наличие нетипизированных указателей в Турбо Паскале (в стандартном Паскале их нет) открывает широкие возможности неявного преобразования типов. К сожалению, трудно обнаруживаемые ошибки в программе, связанные с некорректно используемыми обращениями к процедурам NEW и DISPOSE, также могут привести к нежелательному преобразованию типов. В самом деле, пусть имеется программа:

var
i,j: Integer;
r: Real;
begin
new(i); {i: = HeapOrg; HeapPtr: = HeapOrg + 2}
j: = i; {j: = HeapOrg}
j: = 2;
dispose(i); {HeapPtr: = HeapOrg}
new(r); {r: = HeapOrg; HeapPtr: = HeapOrg + 6}
r: = pi;
WriteLn(j)
end.

Что будет выведено на экран дисплея? Чтобы ответить на этот вопрос, проследим за значениями указателя HEAPPTR. Перед исполнением программы этот указатель имел значение адреса начала кучи HEAPORG, которое и было передано указателю I, азатем и J. После выполнения DISPOSE(I) указатель кучи вновь приобрел значение HEAPORG, этот адрес передан указателю R в процедуре NEW(R). После того как по адресу R разместилось вещественное число pi=3.14159, первые 2 байта кучи оказались заняты под часть внутреннего представления этого числа. В то же время J все еще сохраняет адрес HEAPORG, поэтому оператор WRITELN(J^) будет рассматривать 2 байта числа pi как внутреннее представление целого числа (ведь J – это указатель на тип INTEGER) и выведет 8578.

Если Вы заметили ошибку, выделите, пожалуйста, необходимый текст и нажмите CTRL + Enter, чтобы сообщить об этом редактору.