Ссылки, динамические переменные и структуры

Цель: получить практические навыки использования указателей и динамических структур данных с помощью средств Turbo Pascal.

Распределение памяти при выполнении программ

Адресное пространство компьютера при запуске программы (ЕХЕ-файла) Турбо Паскаля составляет 1 Мбайт и состоит из сегментов по 64 Кбайт. Первые 256 байт памяти отводятся под префикс структуры программы, который содержит служебную информацию о программе. После префикса начинаются сегменты кода и модулей, которые содержат рабочий код основного блока программы, рабочий код системного модуля, рабочие коды подключаемых модулей. После них располагается сегмент данных, где хранятся статические глобальные переменные основного блока и все типизированные константы, включая локальные. За сегментом данных следует область стека. В ней располагаются локальные переменные и параметры-значения процедур и функций во время их работы по вызову. Выше стека отводится память под буфер для работы оверлеев – перекрывающихся частей программ. Еще выше располагается динамически распределяемая область памяти, называемая областью кучи или просто кучей (Heap-областью). Объемом распределяемой динамической памяти можно управлять с помощью директивы компилятора $М.

В динамической области размещаются объекты, память для которых отводится и освобождается непосредственно по ходу выполнения программы. Такие данные, размер которых задается во время выполнения программы, называются динамическими. Для объявления динамических данных в Паскале используется ссылочный тип, называемый еще типом-указателем. Значением переменных ссылочного типа является адрес ячейки памяти. Адрес занимает четыре байта и хранится в виде двух слов, одно из которых определяет сегмент, второе – смещение. В Паскале имеется стандартный тип – указатель и ссылочные типы, определяемые программистом.

Указатель

Величины стандартного типа pointer предназначены для хранения адресов данных произвольного типа. Описание указателя стандартного типа:

var <идентификатор> : pointer;

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

Addr(X):Pointer Ссылка на начало объекта X в памяти. Аналогом этой функции является операция @Х
Seg(X):Word Сегмент, в котором хранится объект X
Ofs(X):Word Смещение в сегменте для объекта X
Ptr(S,O:Word):Pointer Ссылка на место в памяти, заданное значениями смещения О и сегмента S
SizeOf(x):Word Размер объекта X в байтах

Указатели могут обмениваться значениями с помощью оператора присваивания и сравниваться с помощью операций отношения = или <> (не равно). Сравнение для указателей – ненадежная операция, так как два указателя, содержащие один и тот же адрес памяти, но записанный в них разными способами, считаются различными.

Пример 1.

Var р,q : pointer; x:string;

……………………………………………………………………….

p:=addr(x); q:=@p; {p=q}

Writeln('Сегмент ', Seg(p), 'смещение ',Ofs(p));

Так как значение указателя состоит из двух слов (Word), хранящих сегмент и смещение, можно вывести их в отдельности, используя функции Seg и Ofs.

В Паскале есть предопределенная константа nil типа Pointer, соответствующая адресу 0000:0000 (пустая ссылка). Если указателю присвоено значение nil, то этот указатель ни на какие данные не ссылается.

Чтобы обратиться к данным, находящимся по адресу, содержащемуся в указателе, используется символ ^, который ставится сразу после имени указателя. Эта операция называется операцией разыменования. Суть ее состоит в переходе от указателя к значению, на которое он указывает. Пусть p – указатель, то p=addr(p^), и, например, p^=’строка’.

Ссылочные типы

Программист может определить ссылки на данные конкретного типа.

Формат описания ссылочного типа:

type<имя ссылочного типа>=^<имя базового типа>;

Ссылочные переменные указывают на объекты базового типа, определяя динамические переменные.

Пример 2.

type{базовые типы }

dimT=array[1..10000]of real;

recT=recorda,b:integer end;

var{ссылочные типы }

dimP=^ dimT;

recP=^recT;

xP=pointer;

i, j:^integer;

Операция разыменования состоит в переходе от ссылочной переменной к значению объекта, на который она указывает.

Пусть, например, i^=2, j^=5, тогда:
j^:=i^; означает присваивание значения, т.е. j^=2, при этом адреса, записанные в i и j не изменились;
j:=i; здесь адрес, на который указывает i записывается в ссылочную переменную j.

Доступ к полям и элементам массива, например, dimP^[I], recP^.a.

Присваивание dimP:= recP запрещено, поскольку dimP и recP указывают на разные типы данных. Это ограничение не распространяется на нетипизированные указатели. Можно совмещать разные типы, используя указатели, например, xP:= dimP; recP:= xP.

Пример 3.

Program Prog10_1; {программа демонстрации наложения типов}

type t : array[1..2] Of char;

var a :^t; {ссылка на массив }

i : integer;

Begin

i:=19022;

a:=addr(i); {присваиваем а значение адреса переменной i }

write(a^[1],a^[2]); {выводим массив }

End.

Переменная i занимает в памяти 2 байта и в 16-ричном коде равна 4A4E. После передачи адреса переменной a, эти два байта представляют два символа массива: младший байт 4Е (78 десятичное) соответствует элементу a^[1], старший байт 4А (74 десятичное) соответствует элементу a^[2]. Результат: N – код 78 и J – код 74.

Наши рекомендации