7.4.3. Выделение
и освобождение динамической памяти
Вся динамическая память в Object
Pascal рассматривается как сплошной массив байтов, который называется
кучей.
Память под любую динамически размещаемую
переменную выделяется процедурой New. Параметром обращения к этой процедуре
является типизированный указатель. В результате обращения указатель приобретает
значение, соответствующее адресу, начиная с которого можно разместить данные,
например:
var
pI,pJ: ^Integer;
pR: ^Real;
begin
New (pI) ;
New (pR) ;
end;
После того как указатель приобрел
некоторое значение, т. е. стал указывать на конкретный физический байт памяти,
по этому адресу можно разместить любое значение соответствующего типа. Для этого
в операторе присваивания сразу за указателем без каких-либо пробелов ставится
значок
^
, например:
pJ^ := 2; // В
область
памяти pJ помещено значение 2
pl^ := 2*pi; // В
область
памяти pR помещено значение 6.28
Таким образом,
значение,
на
которое указывает указатель, т. е. собственно данные, размещенные в куче, обозначаются
значком ^, который ставится сразу за указателем. Если за указателем нет значка
^
, то имеется в виду
адрес,
по которому размещены данные.
Имеет смысл еще раз задуматься над только что сказанным: значением любого указателя
является адрес, а чтобы указать, что речь идет не об адресе, а о тех данных,
которые размещены по этому адресу, за указателем ставится
^
(иногда
об этом говорят как
о разыменовании
указателя).
Динамически размещенные данные можно
использовать в любом месте программы, где это допустимо для констант и переменных
соответствующего типа, например:
рR^ := Sqr(pR") + I^ - 17;
Разумеется, совершенно недопустим
оператор
pR := Sqr(pR") + I^ - 17;
так как
указателю
pR нельзя
присвоить значение вещественного выражения. Точно так же недопустим оператор
pR
^
:= Sqr(pR)
;
поскольку значением указателя pR
является адрес и его (в отличие от того значения, которое размещено по этому
адресу) нельзя возводить в квадрат. Ошибочным будет и такое присваивание:
pR^' := pJ;
так как вещественным данным, на которые
указывает pR, нельзя присвоить значение указателя (адрес).
Динамическую память можно не только
забирать из кучи, но и возвращать обратно. Для этого используется процедура
Dispose. Например, операторы
Dispose(pJ);
Dispose(pR);
вернут в кучу память, которая ранее
была закреплена за указателями pJ и pR (см. выше).
Замечу, что процедура Dispose (pPtr)
не изменяет значения указателя pPtr, а лишь возвращает в кучу память, ранее
связанную с этим указателем. Однако повторное применение процедуры к свободному
указателю приведет к возникновению ошибки периода исполнения. Освободившийся
указатель программист может пометить зарезервированным словом nil. Помечен ли
какой-либо указатель или нет, можно проверить следующим образом:
const
pR: ^Real
=
NIL;
begin
if
pR = NIL
then
New (pR) ;
Dispose(pR) ;
pR
:= NIL;
end;
Никакие другие операции сравнения
над указателями не разрешены.
Приведенный выше фрагмент иллюстрирует
предпочтительный способ объявления указателя в виде типизированной константы
с одновременным присвоением ему значения nil. Следует учесть, что начальное
значение указателя (при его объявлении в разделе переменных) может быть произвольным.
Использование указателей, которым не присвоено значение процедурой New или другим
способом, не контролируется Delphi и вызовет исключение.
Как уже отмечалось, параметром процедуры
New может быть только типизированный указатель. Для работы с нетипизированными
указателями используются Процедуры GetMem И FreeMem:
GetMem(P, Size); //
резервирование
памяти;
FreeMem(P, Size); //
освобождение
памяти.
Здесь р - нетипизированный указатель;
size - размер в байтах требуемой или освобождаемой части кучи.
Примечание
Испoльзoвaние прцeдyp GetMem/FreeMemMem,
как и вообще вся работа диамияесжой памятью, требует особой осторожности и
тщателвного солюдения простого правила: освобождать нужно ровно столько пайти,
сколько её было зарезервировано, и именно с того адреса, с которого она была
зарезёрвирована.