24. Указатели и динамическая память

Указателями называются переменные и константы, значениями которых яв­ляются адреса. Различаются два вида указателей - обобщенные и типизирован­ные. Обобщенные указатели имеют тип POINTER и могут содержать адреса любых объектов. Типизированные указатели имеют тип ^базовый тип и содер­жат только адреса объектов базового типа. Базовый тип может быть любым, кроме файлового. Существует одна константа-указатель NIL, равная некоторому несуществующему адресу. Указателям можно присваивать адреса переменных, для этого служит операция "адрес": @имя переменной. Существует и обратная операция - "значение": указатель^ - результат этой операции есть значение, за­писанное по адресу, который содержит указатель. Операция "значение" непри­менима к обобщенным указателям.Указателям можно присваивать адреса пере­менных соответствующего типа и другие указатели того же типа (или обобщен­ные указатели). Обобщенному указателю можно присвоить любой указатель. Никакие арифметические операции к адресам не применимы. Запишем програм­му, выполняющую простейшие операции с указателями :

VAR b : Byte; w : Word; pB : AByte; pW : AWord;

BEGIN pB:=@b; pW:=@w; READ(pBA,pWA);

WRITELN(pBA,' ',pWA,' ',pWA DIV pBA);

END.

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

1. FUNCTION Addr(X): Pointer - возвращает адрес переменной X, по-существу, аналогична операции "адрес".

2. FUNCTION Seg(X): Word - возвращает сегментную часть адреса перемен­ной X.

3. FUNCTION Ofs(X): Word - возвращает смещение адреса переменной X. (Полный адрес занимает 4 байта, значение первых двух называют сегментом а последних - смещением).

4. FUNCTION Ptr(Seg,Ofs: Word): Pointer - возвращает адрес, имеющий сегмент Seg и смещение Ofs.

Запишем пример использования этих функций: CONST L : LongInt = 123456789; VAR   p : ARRAY [0..3] OF AByte; i : Byte; BEGIN FOR i:=0 TO 3 DO p[i]:=Ptr(Seg(L),Ofs(L)+i);

WRITELN(p[0]A:4,p[1]A:4,p[2]A:4,p[3]A:4);

END.

Программа выведет байты L в последовательности от младшего к старшему: 21 205 91 7.

Приведенные примеры не очень содержательны, так как указатели главным образом используются для работы с динамической памятью. Динамическая па­мять (или хип) - это область памяти, которую программа может использовать для размещения динамических переменных. В отличие от обычных (или статиче­ских) переменных, память под которые отводится компилятором до начала вы­полнения программы и освобождается после ее завершения, динамическая па­мять распределяется и освобождается в процессе выполнения программы. Необ­ходимость использования динамической памяти обусловлена, в частности, огра­ниченностью сегмента данных: все статические переменные, описанные в про­грамме, не могут занимать более 64К памяти. Динамическая память, как прави­ло, имеет гораздо больший объем. Перечислим стандартные функции и процеду­ры для работы с хипом:

5. FUNCTION MemAvail : LongInt - возвращает размер свободной динами­ческой памяти в байтах.

6. FUNCTION MaxAvail : LongInt - возвращает размер наибольшего сво­бодного участка динамической памяти в байтах.

7. PROCEDURE New(VAR Р:указатель) - отводит участок динамической памяти и присваивает указателю Р адрес этого участка. Размер участка определя­ется базовым типом указателя.

8. PROCEDURE Dispose(VAR Р:указатель) - освобождает участок динами­ческой памяти, адрес которого хранится в указателе, после выполнения процеду­ры значение указателя не определено.

9. PROCEDDURE Mark(VAR Р: Pointer) - записывает состояние динамиче­ской памяти в указатель Р.

10. PROCEDURE Release(VAR Р: Pointer) - возвращает динамическую па­мять к состоянию, записанному в указателе P. Не следует использовать для осво­бождения памяти совместно Dispose и Release.

11. PROCEDURE GetMem(VAR P:Pointer; Size: Word) - распределяет уча­сток динамической памяти размером Size байт и записывает его адрес в указатель

Р.

12. PROCEDURE FreeMem(VAR P:Pointer; Size: Word) - освобождает па­мять, распределенную процедурой GetMem.

Приведем еще две процедуры, имеющие отношение к указателям:

13. PROCEDURE Move(VAR Source,Dest; Count: Word) - копирует Count байт из переменной Source в переменную Dest, причем можно использовать и имена переменных, и указатели с операцией "значение".

14. PROCEDURE FillChar(VAR X; Count:Word; Value) - заполняет Count байт переменной X значением Value. Value может быть либо типа Byte, либо ти­па Char.

Приведем пример использования последних двух процедур безотносительно к динамической памяти - пусть в программе описаны массивы:

A : ARRAY[1..100] OF Integer; B : ARRAY[1..1000] OF Integer; требуется скопировать массив A в последние 100 элементов B, а остальные эле­менты B занулить:

FillChar(B,SizeOf(B),0);

Move(A,Ptr(Seg(B),Ofs(B)+900*SizeOf(Integer))A,SizeOf(A)); Теперь запишем программу, использующую массив, размещенный в динамиче­ской памяти:

CONST Nmax=10000;

TYPE Massiv = ARRAY[1..Nmax] OF Word; VAR p : AMassiv; i : Word;

BEGIN IF MaxAvail<SizeOf(Massiv) THEN BEGIN

WR^TEL^^ хватает памяти'); Halt; END; New(p); Randomize;

FOR i:=1 TO Nmax DO pA[i]:=Random(Nmax);

{ здесь могут быть различные действия с массивом }

FOR i:=1 TO Nmax DO WRITE(pA[i]:5); DISPOSE(p);

END.

С динамическим массивом можно обращаться точно так же, как и с обыч­ным, только вместо имени массива используется конструкция "pA".

Приведем пример использования процедур GetMem и FreeMem. Пусть в программе используется несколько (например, 5) больших массивов, причем в каждый момент времени в работе находится только один из них.

CONST N=6000;

VAR Massiv : ARRAY[1..N] OF Real; VAR p : ARRAY[1..5] OF Pointer; i : Byte; BEGIN FOR i:=1 TO 5 DO BEGIN

{<инициализация i-го массива>}

IF MaxAvail<SizeOf(Massiv) THEN BEGIN

WRITELN('Не хватит памяти для ',i,'-ro массива');

Halt; END;

GetMem(p[i],SizeOf(Massiv)); Move(Massiv,p[i]A,SizeOf(Massiv));

END;

Move(p[2]A,Massiv,SizeOf(Massiv));

{ работаем со вторым массивом ........ }

{ теперь "положим массив на место"}

Move(Massiv,p[2]A,SizeOf(Massiv));

{ и так далее ....    освободим память }

FOR i:=1 TO 5 DO FreeMem(p[i],SizeOf(Massiv));

END.