Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
[ Монахов ] Объектно-ориентированное программирование.doc
Скачиваний:
94
Добавлен:
16.08.2013
Размер:
490.5 Кб
Скачать

2.11. Указатели. Динамические переменные. Динамическое выделение и высвобождение памяти

В языке PASCAL имеются структурные типы особого рода (pointers), получившие название указателей, либо, что то же самое, ссылок. Они предназначены для динамического выделения памяти под данные. В разделе декларации указатели описываются следующим образом: если имеется некий предопределенный или определенный пользователями тип tAnyType, то тип — указатель на переменную var MyVariable:tAnyType описывается как type tpAnyType=^tAnyType, а переменная ссылочного типа может быть описана либо как var pAnyType:tpAnyType, либо сразу как var pAnyType:^tAnyType без специального объявления типа tpAnyType.

В отличие от обычных переменных в ссылочных находятся не сами данные, а только адреса ячеек, в которых эти данные хранятся. Поэтому под ссылочные переменные выделяется одинаковое (4 байта) количество памяти независимо от типа данных, на которые указатель ссылается.

Память под данные выделяется только после использования оператора New, который имеет две формы — процедуры и функции. Изначально указатель ссылается в неопределенное место памяти, что может привести к грубым ошибкам при попытке доступа к данным, на которые он указывает. Ссылочной переменной любого типа может быть присвоено значение nil, означающее, что он никуда не указывает. Попытка доступа к данным через такой указатель также вызовет ошибку, однако перед вызовом указатель может быть проверен на то, не равен ли он nil.

Рассмотрим следующий пример:

procedure PtrDemo1;

var p1,p2:^Integer;

a,b:Integer;

begin

a:=1;

b:=2;

New(p1); {в "куче"(heap) динамически выделена ячейка — область под

"безымянную" переменную типа Integer, указатель p1 настроен

на эту ячейку}

p1^:=a+5; {в эту ячейку записывается значение 6}

p2:=p1; {на эту ячейку теперь указывают и p1, и p2, в ней - число 6}

New(p1); {динамически выделяется еще одна ячейка, p1 переключается на нее,

а p2 остался настроен на прежнюю}

b:=p2^-1; {переменной b присваивается значение, содержащееся в первой

ячейке, уменьшенное на 1,то есть 5}

{значение p1^не определено, p2^= 6}

dispose(p1); {высвобождаем память, выделенную под ячейку2}

dispose(p2); {высвобождаем память, выделенную под ячейку1}

end;

В данном примере видно, что указатель можно использовать не только для динамического выделения памяти, как это происходит с p1 , но и для сохранения программой связи с выделенной ячейкой, для чего используется p2. Если бы мы вовремя не настроили p2 на первую динамически выделенную ячейку памяти, при втором вызове New(p1) мы бы потеряли к ней доступ, указатель p1 "перещелкнулся" бы на вторую динамически выделенную ячейку, и образовывался бы так называемый "мусор" (garbage) в памяти: область, помеченная операционной системой как занятая задачей, но самой задаче уже недоступная.

Следующий тонкий момент при работе с указателями — высвобождение динамически выделенной памяти. К окончанию работы программы вся динамически выделенная память должна быть "возвращена обратно". Это делается с помощью оператора dispose, который "уничтожает" (т.е. высвобождает) ячейку, на которую ссылается указатель. Сам указатель при этом не уничтожается и может быть в дальнейшем использован!

Часто встречающаяся в программах ошибка — использование указателя доступа к данным после того, как ячейка на которую он ссылался, высвобождена. Этот случай аналогичен попытке использования неинициализированных (т.е. "не настроенных" на конкретную ячейку) указателей и часто кончается "зависанием" компьютера, такие указатели часто называют "висящими" ("dangling pointer").

Пример работы с указателями:

program PtrDemo2;

type tpInt=^Integer; {тип "ссылочный на Integer"}

var p1,p2:tpInt;

...

begin

...

New(p1); {выделили ячейку1 и настроили на нее p1}

...

New(p2); {выделили ячейку2 и настроили на нее p2}

...

dispose(p2); {высвободили ячейку2, но p2 "жив"}

p2:=p1; {настроили p2 на ячейку1}

New(p1); {выделили ячейку3 и настроили на нее p1}

...

dispose(p1); {высвободили ячейку3}

...

dispose(p2); {высвободили ячейку1}

end.

Следует помнить, что каждому New (вызванному, к примеру, с указателем p1) должен соответствовать свой dispose(вызванный, к примеру, с p2), и чтобы указатели p1 и p2 указывали бы на одну и ту же ячейку. Попытка лишний раз сделать dispose, т.е. повторно высвободить один и тот же участок памяти, обычно сразу заканчивается аварийной остановкой программы. Иногда указатели используются для получения ссылки на переменную, процедуру или функццию. Для этого в Object Pascal имеется функция addr (вместо нее можно использовать символ @ при соответствующей установке опции компилятора).

Соседние файлы в предмете Информатика