Рассмотрим распределение памяти для выполнимого кода программ на Турбо Паскале (рис. 10.1).
Рис. 10.1
При запуске программы (ЕХЕ-файла) MS-DOS организует в памяти нечто вроде анкеты длиной 256 байт на этот файл, называемой PSP (Program Segment Prefix). Структура PSP описывается в технических руководствах по MS-DOS. Сегмент, с которого начинается отсчет PSP, может быть получен через предопределенную в системной библиотеке (модуле System) переменную PrefixSeg типа Word.
После PSP начинается код ЕХЕ-файла. Он может занимать более одного сегмента. Когда выполняется какой-либо из блоков кода (основной блок, процедуры из модулей), он считается размещенным {187} в некотором сегменте кода. Реальное значение сегмента при этом содержится в регистре CS процессора.
Статические глобальные переменные основного блока и все типизированные константы, включая локальные, располагаются в сегменте данных, который запоминается регистром DS процессора. Общий объем переменных и типизированных констант не может в сумме превышать 64K.
Заметим, что реальный сегмент может быть и меньше чем 64K. В самом деле, он может начинаться где угодно. Если самый последний байт в нем имеет смещение, например 255 ($00FF), то следующий сегмент может отсчитываться от следующего же байта.
Следом за сегментом данных следует область стека. В ней располагаются локальные переменные и параметры-значения процедур и функций во время их работы по вызову. Сегмент стека содержится в регистре SS процессора. Турбо Паскаль отводит под стек один сегмент, и поэтому область стека не может превышать 64K.
Стек заполняется от своей верхней границы (она может быть назначена директивой компилятору $М) по направлению к началу, т.е. к старту сегмента. Специальный регистр SP процессора содержит смещение указателя стека в сегменте SS (указатель стека — это как бы отметка уровня заполнения стека).
|
Имеется предопределенная системная переменная StackLimit типа Word, которая логически примыкает к рассматриваемым вопросам. Она содержит минимальное допустимое значение указателя стека. Когда программа запускается, указатель стека имеет максимальное значение, равное отведенной под стек памяти. При работе стек заполняется «сверху вниз», и указатель как бы снижается. Как только он спустится настолько, что свободная часть стека станет меньше чем значение StackLimit, возникнет ошибка времени счета номер 202 Stack overflow (переполнение стека), и программа прервется.
Нормальное значение StackLimit — это 0, а в режиме компиляции {$N+, E+} — 224. Обычно нет необходимости менять это значение.
Выше стека программа отводит себе память под буфер для работы оверлеев — перекрывающихся частей программы. Если они не используются, то буфер не отводится (подробнее об этом см. гл. 18 «Модуль Overlay»).
Еще выше располагается область памяти для размещения динамических переменных и структур данных, называемая областью кучи или просто кучей. Подробное строение кучи будет описано в разд. 11.3. {188}
Анализ расположения кода и областей данных программы
При программировании на низком уровне или использовании вставок машинных кодов в программу необходимо иметь средства анализа положения программы и ее данных в оперативной памяти. Системная библиотека Турбо Паскаля содержит набор средств для этого. Перечень специальных функций анализа памяти приведен в табл. 10.1.
|
Таблица 10.1
Функция: Тип | Возвращаемое значение |
CSeg: Word | Содержимое регистра CS процессора |
DSeg: Word | Содержимое регистра DS процессора |
SSeg: Word | Содержимое регистра SS процессора |
SPtr: Word | Содержимое регистра SP процессора |
Функции CSeg, DSeg, SSeg и SPtr выдают содержимое регистров CS, DS, SS и SP соответственно. При работе программы ее текущий исполнимый код находится в кодовом сегменте, что фиксируется в регистре CS, а статические переменные основного блока и типизированные константы располагаются в сегменте данных, который запоминается регистром DS. Локальные переменные и параметры процедур и функций при вычислениях располагаются в стеке, и сегмент стека содержится в регистре SS. Последний регистр — SP — содержит смещение указателя стека в сегменте SSeg.
Тип Pointer
В Турбо Паскале предопределен специальный адресный тип Pointer — указатель: можно объявлять переменные, значением которых будет адрес ячейки памяти:
VAR
Р: Pointer; { переменная-указатель }
Значения этого типа занимают 4 байта памяти и содержат адрес какой-либо ячейки памяти. Адрес хранится как два слова: одно из них определяет сегмент, а другое — смещение. Значение указателя не может быть в явном виде выведено на экран или печать. Его надо предварительно расшифровывать. Для работы с указателями {189} вводится специальный набор функций. Кроме того, указатели полностью совместимы со ссылочным типом и могут использоваться (с оговорками) как ссылки. Указатели могут обмениваться значениями через оператор присваивания (:=), и сравниваться операторами = и <>. Но сравнение их — весьма ненадежная операция. Так, когда два указателя содержат один и тот же адрес в памяти, но записанный в них разными способами, они считаются различными. Таким образом, два указателя считаются одинаковыми, если в них записаны одинаковые значения сегмента и смещения. Во всех остальных случаях они считаются неравными. Понятия «больше» и «меньше» к значениям типа Pointer неприменимы.
Если мы хотим указать в программе на значение по указанному в переменной типа Pointer адресу, то мы должны использовать символ «^» после имени указателя, например: Р^. Это есть операция разыменования. Подробно этот вопрос рассмотрен при описании работы со ссылками, а особенности разыменования указателей обсуждаются вместе с работой функции Ptr.