Динамическое распределение памяти. До настоящего момента мы имели дело с переменными, которые размещаются в памяти согласно вполне определенным правилам
До настоящего момента мы имели дело с переменными, которые размещаются в памяти согласно вполне определенным правилам. Так память под глобальные переменные программы выделяется в процессе компиляции, и эти переменные существуют в течение всего времени работы программы. Для локальных переменных, описанных в подпрограмме, память отводится при вызове подпрограммы, при выходе из нее эта память освобождается, а сами переменные прекращают свое существование. Иными словами, распределение памяти во всех случаях осуществляется полностью автоматически. Переменные, память под которые выделяется описанным образом, называют статическими. Под эту категорию подпадают все переменные, объявленные в области описаний программных блоков. Однако Borland Pascal предоставляет возможность создавать новые переменные во время работы программы, сообразуясь с потребностями решаемой задачи, и уничтожать их, когда надобность в них отпадает.
Переменные, созданием и уничтожением которых может явно управлять программист, называют динамическими. Для более полного понимания механизма работы с динамическими переменными следует сначала разобраться в механизме адресации оперативной памяти MS DOS.
Наименьшей адресуемой единицей памяти персонального компьютера, построенного на базе микропроцессоров фирмы Intel и их аналогов, является байт. Таким образом память представляет собой последовательность нумерованных байтов. Для обращения к конкретному байту необходимо знать его номер, который называют его физическим адресом.
Память принято делить на слова, двойные слова и параграфы. Слово имеет длину 2 байта, двойное слово - 4 байта, а параграф - 16 байт.
При работе с памятью используется адресация по схеме «база + смещение». При этом адрес конкретного байта М определяется как адрес некоторого заданного байта Аб (адрес базы) + расстояние до требуемого байта асм (смещение).
В микропроцессорах фирмы Intel (начиная с 18086) в качестве адреса базы используют адреса, кратные 16. Четыре последних бита такого адреса равны 0, и их не хранят, а аппаратно добавляют при вычислении физического адреса.
Непрерывный участок памяти, имеющий длину не более 64 КБ и начинающийся с адреса, кратного 16 (0,16,32, …), называют сегментом. Адрес начала сегмента принимают за базу для всего сегмента. Адрес базы сегмента без последних четырех бит называют сегментным.
Сегментный адрес и смещение имеют размер по 16 бит (слово). Физический адрес, получаемый при их сложении с учетом отброшенных четырех бит (рис. 1), имеет размер 20 бит и может адресовать память объемом 2 20 байт или 1 МБ.
16 бит
20 бит
Рис. 1. Получение физического адреса
Максимальное смещение равно 216-1, что соответствует 64 КБ памяти. Таким образом, относительно одной базы можно адресовать не более 64 КБ памяти, что ограничивает размер сегмента.
Современные модели микропроцессоров используют адреса большей длины с отличающейся схемой получения физического адреса, что учитывается версиями Pascal, предназначенным для работы «под Windows», но принцип адресации по схеме «база +смещение» используется и там.
Программа и данные хранятся в памяти фрагментами, каждый из которых расположен в своем сегменте. Различают три вида сегментов: кодов, данных и стека. В сегментах кодов хранится собственно программа. В сегментах данных размещаются глобальные переменные и константы. Сегмент стека интенсивно используется в процессе выполнения программы: при вызове подпрограмм в стек записывается адрес возврата, в нем размещаются локальные переменные, копии параметров-значений, адреса параметров-переменных и параметров-констант и т.п.
В процессе работы сегментные адреса хранятся в специальных сегментных регистрах:
CS - адрес базы сегмента кодов;
DS - адрес базы сегмента данных;
SS - адрес базы сегмента стека.
Доступ к конкретным участкам сегмента осуществляется через соответствующие смещения.
При записи адреса в память отдельно сохраняются сегментный адрес и смещение (рис. 2).
2 байта 2 байта
Рис.2 Структура записи адреса в память.
В Borland Pascal для работы с адресами используется специальный тип данных - указатель. Данные этого типа включают два поля типа word и хранят соответственно сегментный адрес и смещение.
Различают указатели двух типов: типизированные и нетипизированные.
Типизированные указатели содержат адреса, по которым в памяти размещаются данные определенных типов. Используя эти указатели с данными указанных типов можно выполнять операции, предусмотренные базовым типом. Синтаксическая диаграмма объявления типизированного указателя приведена на рис. 3.
Рис. 3. Синтаксическая диаграмма <Объявление типизированного указателя>
Например:
Type tpi=^integer; {объявляем тип «указатель на целое»}
Varpi:tpi; {объявляем переменную этого типа}
или без предварительного объявления типа:
Var pi: ^integer; {объявляем переменную типа «указатель на целое»}
Нетипизированные указатели хранят просто адреса, которые не связаны с данными конкретных типов. Для их объявления используют зарезервированное слово pointer. Например:
Var p:pointer; ...
Указатели - единственное исключение из общего правила, согласно которому все ресурсы перед использованием должны быть описаны. Для них допускаются описания вида:
Туре рр = ^реrсоn; {тип person еще не определен!}
percon = record {определение типа person}
name: string:
next: pp;
end; ...
Для указателей, которые не хранят никаких адресов, введена константа «нулевой адрес», с именем nil. Константу nil можно присваивать указателю любого типа.
Инициализация указателей. Для объявления инициализированных указателей используют типизированные константы, но единственное значение, которое может быть присвоено указателю при инициализации - это значение nil. Например:
Const p:^real = nil; ...
Операции над указателями. Над значениями указателей возможны следующие операции:
Присваивание. При выполнении этой операции указателю присваивается значение другого указателя или nil. Допускается присваивать указателю только значение того же или неопределенного типа.
Например:
Var pi, p2: ^integer;
рЗ: *real;
p: pointer;
{допустимые операции}
pl:=p2; p:=p3; pl:=p; pl:=nil; p:=nil;
{недопустимые операции}
рЗ:=р2; р1:=рЗ; ...
Получение адреса. Это унарная операция, которая строится из знака операции - символа @ (коммерческое а) и одного операнда - переменной любого типа. Результат операции - указатель типа pointer, который можно присвоить любому указателю.
Например:
Var i: integer;
pi: ^integer;
...
pi:=@i; {указатель pi будет содержать адрес переменной i}
Доступ к данным по указателю (операция разыменования). Чтобы получить доступ к переменной по указателю, необходимо после переменной - типизированного указателя поставить знак «^». Полученное значение имеет тип, совпадающий с базовым типом указателя. Нетипизированные указатели разыменовывать нельзя.
Например:
j:=pi^; {переменной j присваивается значение целого,
расположенного по адресу pi}
рi^:=рi^+2; {целое значение, расположенное по адресу pi,
увеличивается на 2}
В табл. 1 показано, как выполняются операции с указателями
Фрагмент программы | Результат операции | Описание операции | ||||
Const i:integer=1; var pi:^integer; | pi
| Создается инициализированная переменная i и указатель на целое pi | ||||
pi:=@i; | pi
i
| Указателю pi присваивается адрес переменной i | ||||
pi^:=pi^+2 | pi i | Значение, адрес которого находится в pi, увеличивается на 2 | ||||
pi:=nil; | pi
| Запись в pi константы «нулевой адрес» |
Операции отношения. Из всех возможных операций отношения допускаются только операции проверки равенства (=) и неравенства (< >). Эти операции проверяют соответственно равенство и неравенство адресов. Например:
sign:=pl=p2; {переменная sign логического типа получает значение true
или false в зависимости от значений указателей}
или
if pl<>nil then ... {проверка адреса}
Так как в качестве базового типа типизированного указателя может быть использован любой тип, допустимо определять «указатель на указатель». Например, если переменную ppi описать и инициализировать следующим образом:
Const i:integer=l;
Var pi: ^integer;
ppi: ^pi:
pi:=@i;
ppi:=@pi; ...
то будет реализована схема, изображенная на рис. 4.
ppi
pi
|
Рис. 4. Указатель на указатель.
Для получения значения переменной i необходимо дважды применить операцию разыменования. В нашем случае ppi^^ имеет тип
integer и равно 1.
Процедуры и функции, работающие с указателями. Для работы с указателями в Паскале предусмотрены стандартные функции, облегчающие и упрощающие выполнение часто встречающихся операций.
1. Функция ADDR(x): pointer - возвращает адрес объекта х, в качестве которого может ыть указано имя переменной, функции, процедуры. Выполняет те же действия, что и операция @.
2. Функция SEG(x): word - возвращает сегментный адрес указанного объекта.
3. Функция OFS(x): word - возвращает смещение указанного объекта.
4. Функция CSEG: word- возвращает текущее значение сегментного регистра CS - сегментный адрес сегмента кодов.
5. Функция DSEG: word - возвращает текущее значение сегментного регистра DS - сегментный адрес сегмента данных.
6. Функция PTR(seg,ofs:word):pointer - возвращает значение указателя по заданным сегментному адресу seg и смещению ofs.
Преобразование типов данных с использованием типизированных указателей. Как отмечалось ранее, типизированный указатель связывается с некоторым типом данных и адресует вполне определенную область памяти, соответствующую длине внутреннего представления своего типа. Если указателям разного типа присвоить один и тот же адрес, то каждый из них будет рассматривать содержимое области в соответствии с внутренним представлением своего типа. Эта особенность указателей позволяет использовать их для неявного преобразования типа.
Необходимо помнить, что для присвоения разнотипным указателям одного и того же адреса следует использовать нетипизированные указатели, либо задавать абсолютное значение требуемого адреса.
Например:
Var L:longlnt; {длинное целое число}
Pl:*array[1..4] of byte; {указатель на область длиной 4 байта}
k: byte;
Begin
L: =123456789;
P1:=@L; {операция @ возвращает нетипизированный указатель}
k:=P1^[1]; {младший байт внутреннего представления числа L, младший
потому, что числа в памяти для данного типа
компьютеров хранятся с младшего байта}
Контроль корректности значений, полученных в результате выполненных действий, системой не осуществляется, а ложится целиком на программиста.
Дата добавления: 2015-12-01; просмотров: 871;