
B.Pascal 7 & Objects/LR - 362 -
Администратор динамически
распределяемой области памяти DOS
─────────────────────────────────────────────────────────────────
Расширения Borland защищенного режима DOS включают в себя
полный администратор памяти защищенного режима. При выполнении
программы защищенного режима DOS вся доступная память превращает-
ся в глобальную динамически распределяемую область памяти, кото-
рая управляется администратором памяти (подсистемой управления
памятью) защищенного режима. Прикладная программа может получить
доступ к глобальной динамически распределяемой области памяти че-
рез подпрограммы GlobalXXXX модуля WinAPI. Хотя можно распреде-
лять блоки глобальной памяти любого размера, глобальная динами-
чески распределяемая область памяти предназначена только для
больших блоков (1024 байт или более). Для каждого блока глобаль-
ной памяти требуется дополнительно 32 байта (это непроизводитель-
ные затраты), а общее число блоков глобальной памяти не может
превышать 8192.
Примечание: Подробнее расширения Borland защищенного
режима DOS описываются в Главе 17 "Программирование в защи-
щенном режиме DOS".
Borland Pascal включает в себя администратор памяти (который
называют также подсистемой управления памятью), реализующий стан-
дартные процедуры New, Dispose, GetMem и FreeMem. Администратор
памяти использует для всех распределений памяти глобальную дина-
мически распределяемую область. Поскольку глобальная динамически
распределяемая область памяти ограничена 8192 блоками (что оче-
видно меньше, чем может потребоваться для некоторых прикладных
программ), администратор памяти Borland Pascal реализует алгоритм
вторичного распределения сегментов, который улучшает производи-
тельность и допускает распределение существенно большего коли-
чества блоков.
Примечание: Borland Pascal для расширенного режима DOS
не поддерживает схему распределения с помощью процедур MArk
и Release, предусмотренную для реального режима DOS.
Алгоритм вторичного распределения блоков работает следующим
образом. При выделении большого блока администратор памяти просто
выделяет глобальную память с помощью подпрограммы GlobalAlloc.
При выделении маленького блока администратор выделяет более круп-
ный блок памяти, а затем разбивает его (вторично распределяет) по
требованию на более мелкие блоки. Перед тем, как администратор
памяти выделяет новый блок глобальной памяти, который, в свою
очередь, будет распределяться повторно, при распределении малень-
ких блоков повторно используется все доступное пространство вто-
ричного распределения.
Примечание: Об использовании администратора памяти в
DLL подробнее рассказывается в Главе 11 "Динамически компо-
нуемые библиотеки".
B.Pascal 7 & Objects/LR - 363 -
Переменная HeapLimit определяет порог между маленькими и
большими блоками динамически распределяемой памяти. По умолчанию
ее значение равно 1024 байтам. Переменная HeapBlock определяет
размер, используемый администратором памяти при распределении
блоков, выделенных для вторичного распределения. По умолчанию
значение HeapBlock равно 8192 байтам. Значения этих переменных
изменять не следует, но если вы это сделаете, убедитесь, что зна-
чение HeapBlock составляет не меньше четырехкратного размера
HeapLimit.
Переменная HeapAllocFpals определяет значение флагов атрибу-
тов, передаваемых GlobalAlloc, когда администратор памяти распре-
деляет блоки глобальной памяти. По умолчанию ее значение равно
gmem_Moveable.
Переменная HeapError
─────────────────────────────────────────────────────────────────
Переменная HeapError позволяет вам реализовать функцию обра-
ботки ошибки динамически распределяемой области памяти. Эта функ-
ция вызывается каждый раз, когда программа динамического распре-
деления памяти не может выполнить запрос на выделение памяти.
НеаpError является указателем, который ссылается на функцию со
следующим заголовком:
function HeapFunc(Size: word): integer; far;
Заметим, что директива far указывает функции обработки ошиб-
ки динамически распределяемой области не необходимость использо-
вать дальнюю модель вызова.
Функция обработки ошибки динамически распределяемой области
реализуется путем присваивания ее адреса переменной НеаpEror:
HeapError := @HeapFunc;
Функция обработки ошибки динамически распределяемой области
памяти получает управление, когда при обращении к процедурам New
или GetМем запрос не может быть выполнен. Параметр Size содержит
размер блока, для которого не оказалось области памяти соответс-
твующего размера, и функция обработки ошибки динамически распре-
деляемой области произведет попытку освобождения блока, размер
которого не меньше данного размера.
Перед вызовом функции обработки ошибки динамически распреде-
ляемой области памяти администратор динамически распределяемой
памяти пытается выделить свободный блок из блоков вторичного раз-
биения, а также использовать непосредственный вызов функции
GlobalAlloc.
В зависимости от успеха выполнения этой попытки функция об-
работки ошибки динамически распределяемой области возвращает зна-
B.Pascal 7 & Objects/LR - 364 -
чения 0, 1 или 2. Возвращаемое значение 0 свидетельствует о неу-
дачной попытке, что немедленно приводит к возникновению ошибки во
время выполнения программы. Возвращаемое значение 1 также свиде-
тельствует о неудачной попытке, но вместо ошибки этапа выполнения
оно приводит к тому, что процедуры New или GetМем возвращают ука-
затель nil. Наконец, возвращаемое значение 2 свидетельствует об
удачной попытке и вызывает повторную попытку выделить память (ко-
торая также может привести к вызову функции обработки ошибки ди-
намически распределяемой области).
Стандартная обработки функция ошибки динамически распределя-
емой области всегда возвращает значение 0, приводя, таким обра-
зом, к ошибке всякий раз, когда не могут быть выполнены процедуры
New или GetМем. Однако для многих прикладных задач более подходя-
щей является простая функция обработки ошибки динамически распре-
деляемой области, пример которой приведен ниже:
function HeapFunc(Size: Word): Integer; far;
begin
HeapFunc := 1;
end;
Если такая функция реализована, то вместо принудительного
завершения работы программы в ситуации, когда процедуры New или
GetМем не могут выполнить запрос, она будет возвращать пустой
указатель (указатель nil).
B.Pascal 7 & Objects/LR - 365 -
Использование памяти в программах Windows
─────────────────────────────────────────────────────────────────
В данном разделе поясняется использование памяти в програм-
мах Borland Pascal для Windows.
Атрибуты сегментов
─────────────────────────────────────────────────────────────────
Каждый сегмент кода имеет набор атрибутов, определяющих его
поведение при загрузке в память.
Атрибуты MOVEABLE или FIXED
─────────────────────────────────────────────────────────────────
Когда сегмент является перемещаемым (MOVEABLE), Windows,
чтобы удовлетворить потребности в распределяемой памяти, может
перемещать сегмент в физической памяти. Когда сегмент кода фикси-
рованный (FIXED), он не перемещается в физической памяти. Более
предпочтителен атрибут MOVEABLE, и если нет абсолютной необходи-
мости хранить сегмент кода по одному и тому же адресу в физичес-
кой памяти (как бывает в том случае, если он содержит драйвер
прерываний), следует использовать атрибут MOVEABLE.
Атрибуты PRELOAD или DEMANDLOAD
─────────────────────────────────────────────────────────────────
Сегмент кода, имеющий атрибут PRELOAD, при активизации прик-
ладной программы или библиотеки загружается автоматически. Атри-
бут DEMANDLOAD откладывает загрузку сегмента до тех пор, пока
подпрограмма в сегменте действительно не будет вызвана.
Атрибуты DISCARDABLE или PERMANENT
─────────────────────────────────────────────────────────────────
Когда сегмент имеет атрибут DISCARDABLE, Windows при необхо-
димости выделения дополнительной памяти может освобождать память,
занимаемую данным сегментом. Когда прикладная программа обращает-
ся к выгружаемому сегменту (DISCARDABLE), которого нет в памяти,
Windows загружает его сначала из файла .EXE. Это занимает большее
время, чем если бы сегмент был постоянным (PERMANENT), но позво-
ляет прикладной программе при выполнении занимать меньше места.
Грубо говоря, сегмент DISCARDABLE в прикладной программе
Windows очень напоминает оверлейный сегмент в программе DOS.
Изменение атрибутов
─────────────────────────────────────────────────────────────────
По умолчанию сегменту кода назначаются атрибуты MOVEABLE,
PRELOAD и PERMANEMT, но с помощью директивы компилятора $C вы мо-
жете их изменить. Например:
B.Pascal 7 & Objects/LR - 366 -
{$C MOVEABLE DEMANDLOAD DISCARDABLE}
Примечание: Более подробно о директиве $C рассказыва-
ется в Главе 2 ("Директивы компилятора") "Справочного руко-
водства программиста".
В прикладной программе Windows нет необходимости выделять
подсистему управления оверлеями. Администратор памяти Windows
включает в себя полный набор обслуживающих средств, управляемых
атрибутами сегмента кода. Эти средства доступны любой прикладной
программе Windows.
B.Pascal 7 & Objects/LR - 367 -
Сегмент локальных динамических данных
─────────────────────────────────────────────────────────────────
Каждая прикладная программа или библиотека имеет один сег-
мент данных, который называется сегментом локальных динамических
данных и может занимать до 64К. На сегмент локальных динамических
данных всегда указывает регистр сегмента данных DS. Он разделен
на четыре части:
Сегмент локальных динамических данных
┌───────────────────────────────────┐
│ │
│ Локальная динамически распределя- │
│ емая область памяти │
│ │
├───────────────────────────────────┤
│ │
│ Стек │
│ │
├───────────────────────────────────┤
│ │
│ Статические данные │
│ │
├───────────────────────────────────┤
│ │
│ Заголовок задачи │
│ │
└───────────────────────────────────┘
Рис. 21.7 Сегмент локальных динамических данных.
Первый 16 байт сегмента локальных динамических данных всегда
содержат заголовок задачи, в котором Windows сохраняет различную
системную информацию.
Область статических данных содержит все глобальные перемен-
ные и типизированные константы, описанные в прикладной программе
или библиотеке.
Сегмент стека используется для хранения локальных перемен-
ных, распределяемых процедурами и функциями. На входе в приклад-
ную программу регистр сегмента стека SS и указатель стека SP заг-
ружаются таким образом, что SS:SP указывает на первый байт после
области стека в сегменте локальных динамических данных. При вызо-
ве процедур и функций SP перемещается вниз, выделяя память для
параметров, адреса возврата и локальных переменных. Когда подп-
рограмма возвращает управление, процесс изменяется на обратный:
SP увеличивается и принимает то значение, которое было перед вы-
зовом. Используемый по умолчанию размер области стека в автомати-
ческом сегменте данных равен 8К, но с помощью директивы компиля-
тора $M это значение можно изменить.
В отличие от прикладной программы библиотека в сегменте ло-
B.Pascal 7 & Objects/LR - 368 -
кальных динамических данных не имеет области стека. При вызове в
динамически компонуемой библиотеке DLL процедуры или функции ре-
гистр DS указывает на сегмент локальных динамических данных биб-
лиотеки, но пара регистров SS:SP не изменяется. Таким образом,
библиотека всегда использует стек вызывающей прикладной програм-
мы.
Последняя часть в сегменте локальных динамических данных -
локальная динамически распределяемая область. Она содержит все
локальные динамические данные, которые распределялись с помощью
функции LocalAlloc в Windows. По умолчанию локальная динамически
распределяемая область имеет размер 8К, но это значение можно из-
менить с помощью директивы компилятора $M.
Windows допускает, чтобы сегмент локальных динамических дан-
ных был перемещаемым, но Borland Pascal этого не поддерживает.
Сегмент локальных динамических данных прикладной программы или
библиотеки Borland Pascal всегда блокируется, этим обеспечивает-
ся, что селектор (адрес сегмента) сегмента локальных динамических
данных никогда не изменяется. При работе в стандартном или расши-
ренном режиме это не приводит ни к какому ухудшению, поскольку
сегмент сохраняет тот же селектор даже при перемещении в физичес-
кой памяти. Однако в реальном режиме, если требуется расширение
локальной динамически распределяемой области, Windows, возможно,
не сможет этого сделать, поскольку сегмент локальных динамических
данных перемещаться не может. Если ваша прикладная программа ис-
пользует локальную динамически распределяемую область памяти и
должна выполняться в реальном режиме, то следует обеспечить, что-
бы начальный размер локальной динамически распределяемой области
был таким, чтобы он удовлетворял всем потребностям в распределе-
нии локальной динамической области (для этого используется дирек-
тива компилятора $M).
Администратор динамически распределяемой области памяти
─────────────────────────────────────────────────────────────────
Windows поддерживает динамическое распределение памяти в
двух различных динамически распределяемых областях: глобальной
динамически распределяемой области и локальной динамически расп-
ределяемой области.
Примечание: Более подробно о локальной и глобальной
динамически распределяемой области рассказывается в "Руко-
водстве программиста по Windows".
Глобальная динамически распределяемая область - это пул па-
мяти, доступный для всех прикладных программ. Хотя могут выде-
ляться блоки глобальной памяти любого размера, глобальная динами-
чески распределяемая область памяти предназначена только для
"больших" областей памяти (256 байт или более). Каждый блок гло-
бальной памяти имеет избыточный размер 20 байт, и при работе в
стандартной среде Windows в улучшенном режиме 386 существует ог-
B.Pascal 7 & Objects/LR - 369 -
раничение в 8192 блока памяти, только некоторые из которых дос-
тупны для отдельной прикладной программы.
Локальная динамически распределяемая область памяти - это
пул памяти, доступной только для вашей прикладной программы или
библиотеки. Она расположена в верхней части сегмента данных прик-
ладной программы или библиотеки. Общий размер блоков локальной
памяти, которые могут выделяться в локальной динамически распре-
деляемой области, равен 64К, минус размер стека прикладной прог-
раммы и статических данных. По этой причине локальная динамически
распределяемая область памяти лучше подходит для "небольших" бло-
ков памяти (26 байт или менее). По умолчанию размер локальной ди-
намически распределяемой области равен 8К, но с помощью директивы
компилятора $M это значение можно изменить.
Примечание: Borland Pascal не поддерживает механизм
распределения памяти с помощью процедур Mark и Release, ко-
торые предусмотрены в версии для DOS.
Borland Pascal включает в себя подсистему управления динами-
чески распределяемой памятью (администратор памяти), которая реа-
лизует стандартные процедуры New, Dispose, GetMem и FreeMem. Для
всех выделений памяти подсистема динамически управления распреде-
ляемой областью памяти использует глобальную динамически распре-
деляемую область. Поскольку глобальная динамически распределяемая
область памяти имеет системное ограничение в 8192 блока (что оп-
ределенно меньше, чем может потребоваться в некоторых прикладных
задачах), подсистема управления динамически распределяемой об-
ластью памяти Borland Pascal для улучшения производительности и
обеспечения выделения существенно большего числа блоков включает
в себя алгоритм вторичного распределения сегмента.
Примечание: Более подробно об этом рассказывается в
Главе 11 "Динамически компонуемые библиотеки".
Алгоритм вторичного выделения сегмента работает следующим
образом: при распределении большого блока администратор динами-
чески распределяемой области памяти просто выделяет глобальный
блок памяти, используя подпрограмму Windows ClobalAlloc. При вы-
делении маленького блока администратор динамически распределяемой
области памяти выделяет больший блок памяти, а затем делит его на
более мелкие блоки (как требуется). При выделении "маленьких"
блоков перед тем, как администратор динамически распределяемой
области памяти выделит блок глобальной динамически распределяемой
памяти (который будет в свою очередь разбит на блоки), повторно
используются все доступные мелкие блоки.
Границу между маленькими и большими блоками определяется пе-
ременной HeapLimit. По умолчанию она имеет значение 1024 байта.
Переменная HeapBlock определяет размер, который использует под-
система управления динамически распределяемой областью памяти при
выделении блоков для вторичного разбиения. По умолчанию она имеет
значение 8192 байта. Изменять эти значения вам незачем, но если
B.Pascal 7 & Objects/LR - 370 -
вы решите это сделать, убедитесь что HeapBlock имеет значение по
крайней мере в четыре раза превышающее HeapLimit.
Переменная HeapAllocFlags определяет значение флагов атрибу-
тов, передаваемых GlobalAlloc, когда администратор памяти распре-
деляет глобальные блоки. В программе по умолчанию используется
значение gmem_Moveable, а в библиотеке - gmem_Moveable +
gmem_SSEShure.
Блоки глобальной памяти, выделяемые администратором динами-
чески распределяемой области памяти, всегда блокируются непос-
редственно после своего выделения (с помощью GlobalLock) немед-
ленно после своего выделения и не разблокируются, пока не будут
освобождены. Этим обеспечивается, что селекторы (адреса сегмен-
тов) блоков не изменяются. В стандартной среде Windows и улучшен-
ных режимах процессора 386 фиксированные блоки могут, тем не ме-
нее, перемещаться в физической памяти, освобождая место для дру-
гих запросов по выделению памяти, поэтому это не ухудшает произ-
водительности администратора динамически распределяемой области
памяти Borland Pascal. Однако в реальном режиме, если от Windows
требуется расширение локальной динамически распределяемой облас-
ти, администратор памяти Windows, возможно, не сможет переместить
их, чтобы выделить другие блоки. Если ваша прикладная программа
использует локальную динамически распределяемую область и должна
выполняться в реальном режиме, можно рассмотреть при выделении
блоков динамической памяти возможность использования средств
распределения памяти, предоставляемых Windows.
Переменная HeapError
─────────────────────────────────────────────────────────────────
Переменная HeapError позволяет вам реализовать функцию обра-
ботки ошибки динамически распределяемой области памяти. Эта функ-
ция вызывается каждый раз, когда программа динамического распре-
деления памяти не может выполнить запрос на выделение памяти.
НеаpError является указателем, который ссылается на функцию со
следующим заголовком:
function HeapFunc(Size: word): integer; far;
Заметим, что директива far указывает функции обработки ошиб-
ки динамически распределяемой области не необходимость использо-
вать дальнюю модель вызова.
Функция обработки ошибки динамически распределяемой области
реализуется путем присваивания ее адреса переменной НеаpEror:
HeapError := @HeapFunc;
Функция обработки ошибки динамически распределяемой области
памяти получает управление, когда при обращении к процедурам New
или GetМем запрос не может быть выполнен. Параметр Size содержит
B.Pascal 7 & Objects/LR - 371 -
размер блока, для которого не оказалось области памяти соответс-
твующего размера, и функция обработки ошибки динамически распре-
деляемой области попытается освободить блок, размер которого не
меньше данного размера.
Перед вызовом функции обработки ошибки динамически распреде-
ляемой области памяти подсистема динамического распределения па-
мяти пытается выделить свободный блок из блоков вторичного разби-
ения, а также использовать непосредственный вызов функции
GlobalAlloc.
В зависимости от успеха выполнения этой попытки функция об-
работки ошибки динамически распределяемой области возвращает зна-
чения 0, 1 или 2. Возвращаемое значение 0 свидетельствует о неу-
дачной попытке, что немедленно приводит к возникновению ошибки во
время выполнения программы. Возвращаемое значение 1 также свиде-
тельствует о неудачной попытке, но вместо ошибки этапа выполнения
оно приводит к тому, что процедуры New или GetМем возвращают ука-
затель nil. Наконец, возвращаемое значение 2 свидетельствует об
удачной попытке и вызывает повторную попытку выделить память (ко-
торая также может привести к вызову функции обработки ошибки ди-
намически распределяемой области).
Стандартная обработки функция ошибки динамически распределя-
емой области всегда возвращает значение 0, приводя, таким обра-
зом, к ошибке всякий раз, когда не могут быть выполнены процедуры
New или GetМем. Однако для многих прикладных задач более подходя-
щей является простая функция обработки ошибки динамически распре-
деляемой области, пример которой приведен ниже:
function HeapFunc(Size: word) integer; far;
begin
HeapFunc := 1;
end;
Если такая функция реализована, то вместо принудительного
завершения работы программы в ситуации, когда процедуры New или
GetМем не могут выполнить запрос, она будет возвращать пустой
указатель (указатель nil).
B.Pascal 7 & Objects/LR - 372 -
Форматы внутреннего представления данных
─────────────────────────────────────────────────────────────────
Далее описываются форматы внутреннего представления данных
Borland Pascal.
Целочисленные типы
─────────────────────────────────────────────────────────────────
Формат, выбираемый для представления переменной целого типа,
зависит от ее минимальной и максимальной границ:
1. Если обе границы находятся в диапазоне -128..127
(Shotrint - короткое целое), то переменная хранится, как
байт со знаком.
2. Если обе границы находятся в диапазоне 0..255 (Byte -
байтовая переменная), то переменная хранится, как байт
без знака.
3. Если обе границы находятся в диапазоне -32768..32767
(Integer - целое), то переменная хранится, как слово со
знаком.
4. Если обе границы находятся в диапазоне 0..65535 (Word -
переменная длиной в слово), то переменная хранится, как
слово.
5. В противном случае переменная хранится, как двойное сло-
во со знаком (Longint - длинное целое).
Символьный тип
─────────────────────────────────────────────────────────────────
Символьный тип или поддиапазон (отрезок) символьного типа
(Char) хранится, как байт без знака.
Булевский тип
─────────────────────────────────────────────────────────────────
Значения и переменные булевского типа Boolean хранятся как
байт, WordBool - как слово, а LongBool - как значение Longint.
При этом подразумеваются, что они могут принимать значения 0
(Falsе) или 1 (Тruе).
Перечислимый тип
─────────────────────────────────────────────────────────────────
Значения перечислимого типа хранятся, как байт без знака,
если нумерация не превышает 256. В противном случае они хранятся,
как слово без знака.
B.Pascal 7 & Objects/LR - 373 -
Типы с плавающей точкой
─────────────────────────────────────────────────────────────────
Типы значений с плавающей точкой Real, Single, Double,
Extended и Comp (вещественный, с одинарной точностью, с двойной
точностью, с повышенной точностью и сложный) хранятся в виде дво-
ичного представления знака (+ или -), показателя степени и знача-
щей части числа. Представляемое число имеет значение:
+/- значащая_часть Х 2^показатель_степени
где значащая часть числа представляет собой отдельный бит слева
от двоичной десятичной точки (то есть 0 <= значащая часть <= 2).
В следующей далее схеме слева расположены старшие значащие
биты, а справа - младшие значащие биты. Самое левое значение хра-
нится в самых старших адресах. Например, для значения веществен-
ного типа e сохраняется в первом байте, f - в следующих пяти бай-
тах, а s - в старшем значащем бите последнего байта.
Вещественный тип
─────────────────────────────────────────────────────────────────
Шестибайтовое (48-битовое) вещественное число (Real) подраз-
деляется на три поля:
1 39 8
┌───┬──────..───────┬────────┐
│ s │ f │ e │
└───┴──────..───────┴────────┘
msb lsb msb lsb
Значение v числа определяется с помощью выражений:
if 0 < e <= 255, then v = (-1)^s * 2^(e-129)*(l.f).
if e = 0, then v = 0.
Вещественный тип не может использоваться для хранения ненор-
мализованных чисел, значений, не являющихся числом (NaN), а также
бесконечно малых и бесконечно больших значений. Ненормализованное
число при сохранении его в виде вещественного принимает нулевое
значение, а не числа, бесконечно малые и бесконечно большие зна-
чения при попытке использовать для их записи формат вещественного
числа приводят к ошибке переполнения.
Здесь и далее msb означает более значащий бит (старшие раз-
ряды), lsb - менее значащий (младшие разряды).
B.Pascal 7 & Objects/LR - 374 -
Тип числа с одинарной точностью
─────────────────────────────────────────────────────────────────
Четырехбайтовое (32-битовое) число типа Single подразделяет-
ся на три поля:
1 8 23
┌───┬──────┬───────..─────────┐
│ s │ e │ f │
└───┴──────┴───────..─────────┘
msb lsb msb lsb
Значение v этого числа определяется с помощью выражений:
if 0 < e < 255, then v = (-1)^s * 2^(e-12) * (l.f).
if e = 0 and f <> 0, then v = (-1)^s * 2^(126) * (o.f).
if e = 0 and f = 0, then v = (-1)^s * O.
if e = 255 and f = 0, then v = (-1)^s * Inf.
if e = 255 and f <> 0, then v = NaN.
Тип числа с двойной точностью
─────────────────────────────────────────────────────────────────
Восьмибайтовое (64-битовое) число типа Double подразделяется
на три поля:
1 11 52
┌───┬──────┬───────..────────┐
│ s │ e │ f │
└───┴──────┴───────..────────┘
msb lsb msb lsb
Значение v этого числа определяется с помощью выражений:
if 0 < e < 2047, then v = (-1)^s * 2^(e-1023) * (l.f).
if e = 0 and f <> 0, then v = (-1)^s * 2^(1022) * (o.f).
if e = 0 and f = 0, then v = (-1)^s * O.
if e = 2047 and f = 0, then v = (-1)^s * Inf.
if e = 2047 and f <> 0, then v = NaN.
B.Pascal 7 & Objects/LR - 375 -
Тип числа с повышенной точностью
─────────────────────────────────────────────────────────────────
Десятибайтовое (80-битовое) число типа Extended подразделя-
ется на четыре поля:
1 15 1 63
┌───┬────────┬───┬────────..───────┐
│ s │ e │ i │ f │
└───┴────────┴───┴────────..───────┘
msb lsb msb lsb
Значение v этого числа определяется с помощью выражений:
if 0 < e < 32767, then v = (-1)^s * 2^(e-1023) * (l.f).
if e = 32767 and f = 0, then v = (-1)^s * Inf.
if e = 32767 and f <> 0, then v = NaN.
Сложный тип
─────────────────────────────────────────────────────────────────
Восьмибайтовое (64-битовое) число сложного типа (Comp) под-
разделяется на два поля:
1 63
┌───┬───────────..──────────────┐
│ s │ d │
└───┴───────────..──────────────┘
msb lsb
Значение v этого числа определяется с помощью выражений:
if s = 1 and d = 0, then v = NaN.
в противном случае v представляет собой 64-битовое значение, яв-
ляющееся дополнением до двух.
Значения типа указатель
─────────────────────────────────────────────────────────────────
Значение типа указатель хранится в виде двойного слова, при
этом смещение хранится в младшем слове, а адрес сегмента - в
старшем слове. Значение указателя nil хранится в виде двойного
слова, заполненного 0.
B.Pascal 7 & Objects/LR - 376 -
Значения строкового типа
─────────────────────────────────────────────────────────────────
Строка занимает столько байт, какова максимальная длина
строки, плюс один байт. Первый байт содержит текущую динамическую
длину строки, а последующие байты содержат символы строки. Бит
длины и символы рассматриваются, как значения без знака. Макси-
мальная длина строки - 255 символов, плюс байт длины
(string[255]).
Значения множественного типа
─────────────────────────────────────────────────────────────────
Множество - это массив бит, в котором каждый бит указывает,
является элемент принадлежащим множеству или нет. Максимальное
число элементов множества - 256, так что множество никогда не мо-
жет занимать более 32 байт. Число байт, занятых отдельным мно-
жеством, вычисляется, как:
ByteSize = (Max div 8) - (Min div 8) + 1
где Мin и Мах - нижняя и верхняя граница базового типа этого мно-
жества. Номер байта для конкретного элемента E вычисляется по
формуле:
ByteNumber = (E div 8) - (Min div 8)
а номер бита внутри этого байта по формуле:
BitNumber = E mod 8
где E обозначает порядковое значение элемента.
Значения типа массив
─────────────────────────────────────────────────────────────────
Массив хранится в виде непрерывной последовательности пере-
менных, каждая из которых имеет тип массива. Элементы с наимень-
шими индексами хранятся в младших адресах памяти. Многомерный
массив хранится таким образом, что правый индекс возрастает быст-
рее.
Значения типа запись
─────────────────────────────────────────────────────────────────
Поля записи хранятся, как непрерывная последовательность пе-
ременных. Первое поле хранится в младших адресах памяти. Если в
записи содержатся различные части, то каждая часть начинается с
одного и того же адреса памяти.
B.Pascal 7 & Objects/LR - 377 -
Объектные типы
─────────────────────────────────────────────────────────────────
Внутренний формат данных объекта имеет сходство с внутренним
форматом записи. Поля объекта записываются в порядке их описаний
как непрерывная последовательность переменных. Любое поле, унас-
ледованное от родительского (порождающего) типа, записывается пе-
ред новыми полями, определенными в дочернем (порожденном) типе.
Если объектный тип определяет виртуальные методы, конструк-
тор или деструктор, то компилятор размещает в объектном типе до-
полнительное поле данных. Это 16-битовое поле, называемое полем
таблицы виртуальных методов (VMP), используется для запоминания
смещения таблицы виртуальных методов в сегменте данных. Поле таб-
лицы виртуальных методов следует непосредственно после обычных
полей объектного типа. Если объектный тип наследует виртуальные
методы, конструкторы или деструкторы (сборщики мусора), то он
также наследует и поле таблицы виртуальных методов, благодаря че-
му дополнительное поле таблицы виртуальных методов не выделяется.
Инициализация поля таблицы виртуальных методов экземпляра
объекта осуществляется конструктором (или конструкторами) объект-
ного типа. Программа никогда не инициализирует поле таблицы вир-
туальных методов явно и не имеет к нему доступа.
Следующие примеры иллюстрируют внутренние форматы данных
объектных типов.
type
PLocation = ^TLocation;
TLocation = object
X,y: integer;
procedure Init(PX, PY: Integer);
function GetX: Integer;
function GetY: Integer;
end;
PPoint = ^TPoint;
TPoint = object(TLocation)
Color: Integer;
constructor Init(PX, PY, PColor: Integer);
destructor Done; virtual;
procedure Show; virtual;
procedure Hide; virtual;
procedure MoveTo(PX, PY: I+nteger); virtual;
end;
PCircle = ^TCircle;
TCircle = object(TPoint)
Radius: Integer;
constructor Init(PX, PY, PColor, PRadius: Integer);
procedure Show; virtual;
B.Pascal 7 & Objects/LR - 378 -
procedure Hide; virtual;
procedure Fill; virtual;
end;
Рисунок 21.8 показывает размещение экземпляров типов
TLocation, TPoint и TCircle: каждый прямоугольник соответствует
одному слову памяти.
TLocation TPoint TCircle
┌──────────┐ ┌───────────┐ ┌───────────┐
│ X │ │ X │ │ X │
├──────────┤ ├───────────┤ ├───────────┤
│ Y │ │ Y │ │ Y │
└──────────┘ ├───────────┤ ├───────────┤
│ Color │ │ Color │
├───────────┤ ├───────────┤
│ VMT │ │ VMT │
└───────────┘ ├───────────┤
│ Radius │
└───────────┘
Рис. 21.8 Схема экземпляров типов TLocation, TPoint и
TCircle.
Так как TPoint является первым типом в иерархии, который
вводит виртуальные методы, то поле таблицы виртуальных методов
размещается сразу после поля Color.
Таблица виртуальных методов
─────────────────────────────────────────────────────────────────
Каждый объектный тип, содержащий или наследующий виртуальные
методы, конструкторы или деструкторы, имеет связанную с ним таб-
лицу виртуальных методов, в которой запоминается инициализируемая
часть сегмента данных программы. Для каждого объектного типа (но
не для каждого экземпляра) имеется только одна таблица виртуаль-
ных методов, однако два различных объектных типа никогда не раз-
деляют одну таблицу виртуальных методов, независимо от того, нас-
колько эти типы идентичны. Таблицы виртуальных методов создаются
автоматически компилятором, и программа никогда не манипулирует
ими непосредственно. Аналогично, указатели на таблицы виртуальных
методов автоматически запоминаются в реализациях объектных типов
с помощью конструкторов программа никогда не работает с этими
указателями непосредственно.
Первое слово таблицы виртуальных методов содержит размер
экземпляров соответствующего объектного типа. Эта информация ис-
пользуется конструкторами и деструкторами для определения того,
сколько байт выделяется или освобождается при использовании рас-
ширенного синтаксиса стандартных процедур New и Dispose.
Второе слово таблицы виртуальных методов содержит отрица-
B.Pascal 7 & Objects/LR - 379 -
тельный размер экземпляров соответствующего объектного типа эта
информация используется ратификационным (т.е. подтверждающим
действительность) механизмом вызова виртуального метода для выяв-
ления инициализируемых объектов (экземпляров, для которых должен
выполняться конструктор) и для проверки согласованности таблицы
виртуальных методов. Когда разрешена ратификация виртуального вы-
зова (с помощью директивы {$R+} компилятора, которая расширена и
включает в себя проверку виртуальных методов), компилятор генери-
рует вызов программы ратификации таблицы виртуальных методов пе-
ред каждым вызовом виртуального метода. Программа ратификации
таблицы виртуальных методов проверяет, что первое слово таблицы
виртуальных методов не равно нулю и что сумма первого и второго
слов равна нулю. Если любая из проверок неудачна, то генерируется
ошибка 210 исполняющей системы Borland Pascal.
Разрешение проверок границ диапазонов и проверок вызовов
виртуальных методов замедляет выполнение программы и делает ее
несколько больше, поэтому используйте {$R+} только во время от-
ладки и переключите эту директиву в состояние {$R-} в окончатель-
ной версии программы.
Наконец, начиная со смещения 4 таблицы виртуальных методов
следует список 32-разрядных указателей методов, один указатель на
каждый виртуальный метод в порядке их описаний. Каждая позиция
содержит адрес точки входа соответствующего виртуального метода.
B.Pascal 7 & Objects/LR - 380 -
На Рис. 21.9 показано размещение таблиц виртуальных методов
типов Point и Circle (тип Location не имеет таблицы виртуальных
методов, т.к. не содержит в себе виртуальных методов, конструкто-
ров и деструкторов): каждый маленький прямоугольник соответствует
одному слову памяти, а каждый большой прямоугольник - двум словам
памяти.
Point VMT Circle VMT
┌───────────────┐ ┌────────────────┐
│ 8 │ │ 8 │
├───────────────┤ ├────────────────┤
│ -8 │ │ -8 │
├───────────────┤ ├────────────────┤
│ 0 │ │ 0 │
├───────────────┤ ├────────────────┤
│ 0 │ │ 0 │
├───────────────┤ ├────────────────┤
│ │ │ │
│ @TPoint.Done │ │ @TPoint.Done │
│ │ │ │
├───────────────┤ ├────────────────┤
│ │ │ │
│ @TPoint.Show │ │ @TCircle.Show │
│ │ │ │
├───────────────┤ ├────────────────┤
│ │ │ │
│ @TPoint.Hide │ │ @TCircle.Hide │
│ │ │ │
├───────────────┤ ├────────────────┤
│ │ │ │
│ @TPoint.MoveTo│ │ @TPoint.MoveTo │
│ │ │ │
└───────────────┘ ├────────────────┤
│ │
│ @TCircle.Fill │
│ │
└────────────────┘
Рис. 21.9 Схемы таблиц виртуальных методов для TPoint и
TCircle.
Обратите внимание на то, как TCircle наследует методы Done и
MoveTo типа TPoint и как он переопределяет Show и Hide.
Как уже упоминалось, конструкторы объектных типов содержат
специальный код, который запоминает смещение таблицы виртуальных
методов объектного типа в инициализируемых экземплярах. Например,
если имеется экземпляр P типа TPoint и экземпляр C типа TCircle,
то вызов P.Init будет автоматически записывать смещение таблицы
виртуальных методов типа TPoint в поле таблицы виртуальных мето-
дов экземпляра P, а вызов C.Init точно так же запишет смещение
таблицы виртуальных методов типа TCircle в поле таблицы виртуаль-
B.Pascal 7 & Objects/LR - 381 -
ных методов экземпляра C. Эта автоматическая инициализация явля-
ется частью кода входа конструктора, поэтому если управление пе-
редается в начало операторной секции, то поле Self таблицы вирту-
альных методов также будет установлено. Таким образом, при воз-
никновении необходимости, конструктор может выполнить вызов вир-
туального метода.
Таблица динамических методов
─────────────────────────────────────────────────────────────────
Таблица виртуальных методов объектного типа содержит для
каждого описанного в объектном типе виртуального метода и его
предков четырехбайтовую запись. В тех случаях, когда в порождаю-
щих типах (предках) определяется большее число виртуальных мето-
дов, в процессе создания производных типов может использоваться
достаточно большой объем памяти, особенно если создается много
производных типов. Хотя в производных типах могут переопределять-
ся только некоторые из наследуемых методов, таблица виртуальных
методов каждого производного типа содержит указатели метода для
всех наследуемых виртуальных методов, даже если они не изменя-
лись.
Динамические методы обеспечивают в таких ситуациях альтерна-
тиву. В Borland Pascal имеется формат таблицы методов и новый
способ диспетчеризации методов с поздним связыванием. Вместо ко-
дирования для всех методов объектного типа с поздним связыванием,
в таблице динамических методов кодируются только те методы, кото-
рые были в объектном типе переопределены. Если в наследующих ти-
пах переопределяются только некоторые из большого числа методов с
поздним связыванием, формат таблицы динамических методов исполь-
зует меньшее пространство, чем формат таблицы виртуальных мето-
дов.
B.Pascal 7 & Objects/LR - 382 -
Формат таблицы динамических методов иллюстрируют следующие
два объектных типа:
type
TBase = object