Ссылки, динамические переменные и структуры
Цель: получить практические навыки использования указателей и динамических структур данных с помощью средств 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.