В данной главе описывается, как программы Borland Pascal используют память.  Borland Pascal может создавать прикладные программы для  реального  режима DOS,  защищенного  режима DOS,  и  Windows;  в  каждом типе прикладной программы память используется  по-разному. В данной главе поясняется, как использует память каждый из этих типов программ. Мы рассмотрим также внутренние форматы данных,  подсистему управления динамически распределяемой  областью памяти и прямой доступ к памяти.

На Рис.  21.1 приведена схема распределения памяти программы  Borland Pascal, для реального режима DOS.

Префикс программного сегмента (PSP) - это область длиной 256  байт, которая строится операционной системой DOS  при  загрузке  файла .EXE.  Адрес  PSP  сохраняется в  предописанной переменной  Borland Pascal длиной в слово с именем PrefixSeg.

Каждой программе (которая включает в себя основную программу  и каждый модуль) соответствует сегмент ее кода. Основная программа занимает первый сегмент кода. Следующие сегменты кода заняты  модулями (в порядке,  обратном тому, в котором они указаны в операторе uses).  Последний сегмент кода занят библиотекой исполняющей системы (модуль System). Размер отдельного сегмента не может  превышать 64К,  однако общий размер кода ограничен только объемом  имеющейся памяти.

 Верхняя граница памяти DOS

  HeapEnd   ДД>ЪДДДДДДДДДДДДДДДДДДДДДДДДДДДДї

   і і

   і   свободная память   і

   і    і

  HeapPtr   ДД>і............................і

   і динамически распределяемая і

   і   область памяти  і

   і   (растет вверх) ^   і

  HeapOrg  ДД>ГДДДДДДДДДДДДДДДДДДДДДДДДДДДДґ<ДД  OvrHeapEnd

   і   оверлейный буфер   і

  ГДДДДДДДДДДДДДДДДДДДДДДДДДДДДґ<ДД OvrHeapOrg

   і  стек (растет вниз) v   і

  SSeg:SPtr ДД>і............................і

   і свободный стек і

  SSeg:0000 ДД>ГДДДДДДДДДДДДДДДДДДДДДДДДДДДДґ

   і   глобальные переменные і

   і............................і<ДДДДДДДї

   і   типизированные константы і  і

  DSeg:0000 ДД>ГДДДДДДДДДДДДДДДДДДДДДДДДДДДДґ і

   і   кодовый сегмент і  і

   і модуля System і  і 

   і............................і  і

   і   кодовый сегмент і  і

   і первого модуля і  і

   і............................і  і

  АДДДДДДДДДДДДДДДДДДДДДДДДДДДДЩсодержимое

   .   кодовый сегмент .   образа

   .   других  модулей   . файла .EXE

  ЪДДДДДДДДДДДДДДДДДДДДДДДДДДДДї і

  і............................і і

   і   кодовый сегмент і  і

   і последнего  модуля   і  і

  ГДДДДДДДДДДДДДДДДДДДДДДДДДДДДґ і

   і   кодовый сегмент і  і

   і  главной программы   і  і

   ГДДДДДДДДДДДДДДДДДДДДДДДДДДДДґ<ДДДДДДДЩ

   і префикс сегмента программы і

   і  (PSP)   і

  PrefixSeg ДД>АДДДДДДДДДДДДДДДДДДДДДДДДДДДДЩ

Рис. 21.1 Схема памяти для программы реального режима DOS.

Сегмент данных  (адресуемый через  регистр DS) содержит все  типизированные константы,  за которыми следуют все глобальные переменные.  В  процессе выполнения программы регистр DS никогда не изменяется. Размер сегмента данных не может превышать 64К.

При входе в программу регистр сегмента стека (SS)  и  указатель стека (SР) загружаются так,  что пара регистров SS:SР указывает на первый байт,  следующий за сегментом стека.  Регистр SS в  процессе  выполнения программы никогда не изменяется,  а SP может 

   B.Pascal 7 & Objects/LR  - 350 -

  перемещаться вниз,  пока не достигнет  нижней  границы  сегмента.

Размер  сегмента  стека не может превышать 64К.  По умолчанию ему  назначается размер, равный 16К, но с помощью директивы компилятора $М это значение можно изменить.

Оверлейный буфер  используется  стандартным  модулем Overlay  для хранения оверлейного кода.  По умолчанию  размер  оверлейного  буфера соответствует размеру наибольшего оверлея в программе. Если программа не имеет оверлеев,  то размер оверлейного буфера будет нулевым.  Размер оверлейного буфера можно увеличить с помощью  вызова подпрограммы OvrSetBuf модуля Overlay.  В этом случае размер динамически   распределяемой  области  памяти  соответственно  уменьшается, а HeapOrg перемещается вверх.

В динамически распределяемой области сохраняются  динамические переменные,  то есть переменные,  выделенные при обращениях к  стандартным процедурам New и GetMem.  Она занимает всю  свободную  память  или  часть  свободной памяти,  оставшуюся при выполнении  программы.  Действительный размер динамически распределяемой  области зависит от максимального и минимального значений,  которые  можно установить для динамически распределяемой области с помощью  директивы компилятора $М.  Гарантированный минимальный размер динамически распределяемой области не может быть меньше минимального значения,  установленного для этой области. По умолчанию минимальные размер динамически распределяемой области равен 0 байт, а  максимальный - 640К;  это означает,  что по умолчанию динамически  распределяемая область занимает всю доступную память.

Подсистема динамического  распределения  памяти  (являющаяся  частью библиотеки исполняющей системы), как можно догадаться, управляет динамически распределяемой областью. Детально она  описывается в следующем разделе.

Динамически распределяемая область -  это  похожая  на  стек  структура,  которая увеличивается, начиная от младших адресов памяти. При  этом  используется  сегмент динамически распределяемой  области. Нижняя граница динамически распределяемой области  запоминается  в  переменной  HеаpOrg, а  верхняя граница динамически  распределяемой области соответствует нижней границе свободной памяти и сохраняется в переменной НеаpPtr. При каждом выделении динамической переменной в динамически распределяемой  области  подсистема динамического распределения памяти (администратор динамически распределяемой области) перемещает переменную HeapPtr вверх  на размер переменной, как бы организуя при этом стек динамических  переменных, в котором одна переменная размещается над другой.

Переменная НеаpPtr после каждой операции как правило  нормализуется,  и смещение, таким образом, принимает значения в диапазоне от $0000 до $000F.  Так как каждая переменная должна целиком  содержаться в одном сегменте, максимальный размер отдельной переменной, которая может быть размещена в динамически распределяемой  области,  составляет  65521  байт (что соответствует $10000 минус  $000F).

Динамические переменные, сохраняемые в динамически распределяемой области, освобождаются одним из двух следующих способов:

1.  С помощью процедур Dispose или FrееМем.

2.  С помощью процедур Маrk и Rеlеаsе.

Простейшей схемой использования  процедур  Маrk и  Rеlеаsе,  например, является выполнение следующих операторов:

New(Ptr1);

New(Ptr2);

Mark(P);

New(Ptr3);

New(Ptr4);

New(Ptr5);

Схема динамически распределяемой области при этом будет выглядеть, как показано на Рис. 21.2.

HeapEnd   ДД>ЪДДДДДДДДДДДДДДДДДДДДДДДДДДї Верхняя граница

  і  і памяти

  і  і

HeapPtr   ДД>ГДДДДДДДДДДДДДДДДДДДДДДДДДДґ

  і   содержимое Ptr5^ і

  Ptr5  ДД>ГДДДДДДДДДДДДДДДДДДДДДДДДДДґ

  і   содержимое Ptr4^ і

  Ptr4  ДД>ГДДДДДДДДДДДДДДДДДДДДДДДДДДґ

 і   содержимое Ptr3^ і

  Ptr3  ДД>ГДДДДДДДДДДДДДДДДДДДДДДДДДДґ

  і   содержимое Ptr2^ і

  Ptr2  ДД>ГДДДДДДДДДДДДДДДДДДДДДДДДДДґ

  і   содержимое Ptr1^ і

  Ptr1  ДД>АДДДДДДДДДДДДДДДДДДДДДДДДДДЩ Нижняя граница памяти

Рис. 21.2  Метод освобождения областей динамически распределяемой области помощью процедур Маrk и Rеlеаsе.

Оператор Маrk(P) отмечает состояние динамически распределяемой области непосредственно перед выделением памяти для переменной Ptr3 (путем сохранения текущего значения переменной НеаpPtr в  P). Если  выполняется оператор Rеleаsе(P),  то схема динамически  распределяемой области становится такой,  как  показано  на  Рис.  21.3. При  этом,  поскольку производится  обращение к процедуре  Маrk, освобождается память, выделенная под все указатели.

Примечание: Выполнение процедуры Rеleаsе(НеаpОrg) полностью освобождает динамически распределяемую область памяти, поскольку переменная НеаpOrg указывает на нижнюю границу динамически распределяемой области.

HeapEnd   ДД>ЪДДДДДДДДДДДДДДДДДДДДДДДДДДї Верхняя граница

  і  і памяти

  і  і

  і  і

  і  і

  і  і

  і  і

HeapPtr   ДД>ГДДДДДДДДДДДДДДДДДДДДДДДДДДґ

  і   содержимое Ptr2^ і

  Ptr2  ДД>ГДДДДДДДДДДДДДДДДДДДДДДДДДДґ

  і   содержимое Ptr1^ і

  Ptr1  ДД>АДДДДДДДДДДДДДДДДДДДДДДДДДДЩ Нижняя граница памяти

Рис. 21.3  Схема динамически распределяемой области при выполнении процедуры Rеleаsе(P).

Применение процедур Маrk и Rеlеаsе для  освобождения памяти,  выделенной для динамических переменных, на которые ссылаются указатели, выполняемое в порядке,  в точности обратном порядку выделения памяти,  весьма эффективно.  Однако в большинстве программ  имеется тенденция в более случайному выделению и освобождению памяти, отведенной для динамических переменных, на которые ссылаются указатели, что влечет за собой необходимость использования более тонких методов управления памятью,  которые реализованы с помощью процедур Dispose и FrееMem. Эти процедуры позволяют в любой  момент освободить память, выделенную для любой динамической переменной, на которую ссылается указатель.

Когда с помощью процедур Dispose и FrееМем освобождается память, отведенная для динамической переменной, не являющаяся "самой верхней" переменной в динамически распределяемой  области, то  динамически  распределяемая область становится фрагментированной.  Предположим, что выполнялась та же последовательности операторов,  что и  в  предыдущем  примере.  Тогда после выполнения процедуры  Dispose(Ptr3) в центре динамически распределяемой  области памяти  образуется незанятое пространство ("дыра").  Это показано на Рис.  21.4.

HeapEnd   ДД>ЪДДДДДДДДДДДДДДДДДДДДДДДДДДї Верхняя граница

     і  і памяти

  і  і

HeapPtr   ДД>ГДДДДДДДДДДДДДДДДДДДДДДДДДДґ

  і   содержимое Ptr5^ і

  Ptr5  ДД>ГДДДДДДДДДДДДДДДДДДДДДДДДДДґ

  і   содержимое Ptr4^ і

  Ptr4  ДД>ГДДДДДДДДДДДДДДДДДДДДДДДДДДґ

 і±±±±±±±±±±±±±±±±±±±±±±±±±±і

 і±±±±±±±±±±±±±±±±±±±±±±±±±±і

  ГДДДДДДДДДДДДДДДДДДДДДДДДДДґ

  і   содержимое Ptr2^ і

  Ptr2  ДД>ГДДДДДДДДДДДДДДДДДДДДДДДДДДґ

  і   содержимое Ptr1^ і

  Ptr1  ДД>АДДДДДДДДДДДДДДДДДДДДДДДДДДЩ Нижняя граница памяти

Рис. 21.4 Создание незанятой области ("дыры")  в динамически  распределяемой области памяти.

Если в данный момент выполняется процедура New(Ptr3), то это  опять приведет к выделению той же области памяти. С другой стороны, выполнение процедуры Dispose(Ptr4) увеличит размер свободного  блока,  так как Ptr3 и Ptr4 были  соседними  блоками (см.  Рис.  21.5).

HeapEnd   ДД>ЪДДДДДДДДДДДДДДДДДДДДДДДДДДї Верхняя граница

  і  і памяти

  і  і

HeapPtr   ДД>ГДДДДДДДДДДДДДДДДДДДДДДДДДДґ

  і   содержимое Ptr5^ і

  Ptr5  ДД>ГДДДДДДДДДДДДДДДДДДДДДДДДДДґ

  і±±±±±±±±±±±±±±±±±±±±±±±±±±і

 і±±±±±±±±±±±±±±±±±±±±±±±±±±і

 і±±±±±±±±±±±±±±±±±±±±±±±±±±і

 і±±±±±±±±±±±±±±±±±±±±±±±±±±і

 ГДДДДДДДДДДДДДДДДДДДДДДДДДДґ

 і   содержимое Ptr2^ і

  Ptr2  ДД>ГДДДДДДДДДДДДДДДДДДДДДДДДДДґ

  і   содержимое Ptr1^ і

  Ptr1  ДД>АДДДДДДДДДДДДДДДДДДДДДДДДДДЩ Нижняя граница памяти

Рис. 21.5 Увеличение размера незанятого блока памяти.

В конечном итоге выполнение процедуры Dispose(Ptr5) приведет  сначала  к  созданию  незанятого блока большего размера,  а затем  НеаpPtr переместится в более  младшие адреса  памяти.  Поскольку последним  допустимым  указателем теперь  будет  Ptr2 (см. Рис.  21 6),  то это приведет к действительному освобождению незанятого  блока.

HeapEnd   ДД>ЪДДДДДДДДДДДДДДДДДДДДДДДДДДї Верхняя граница

  і  і памяти

  і  і

  і  і

  і  і

  і  і

  і  і

  і  і

  і  і

HeapPtr   ДД>ГДДДДДДДДДДДДДДДДДДДДДДДДДДґ

  і   содержимое Ptr2^ і

  Ptr2  ДД>ГДДДДДДДДДДДДДДДДДДДДДДДДДДґ

  і   содержимое Ptr1^ і

  Ptr1  ДД>АДДДДДДДДДДДДДДДДДДДДДДДДДДЩ Нижняя граница памяти

Рис. 21.7 Освобождение незанятого блока памяти.

Как показано  на Рис. 21.7,  динамически распределяемая область памяти теперь находится в том же самом состоянии,  в  каком  она находилась бы после выполнения процедуры Rеlеаsе(P).  Однако  создаваемые и освобождаемые при таком  процессе  незанятые блоки  отслеживаются для их возможного повторного использования.

Адреса и размеры свободных блоков,  созданных при  операциях  Dispose  и FrееМем,  хранятся в списке свободных блоков,  который  увеличивается вниз, начиная со старших адресов памяти, в сегменте  динамически  распределяемой области. Каждый раз перед выделением  памяти для динамической переменной,  перед тем,  как динамически  распределяемая  область будет расширена,  проверяется список свободных блоков.  Если имеется блок  соответствующего  размера  (то  есть размер которого больше или равен требуемому размеру),  то он  используется.

Процедура Rеlеаsе всегда очищает  список  свободных блоков.  Таким образом,  программа динамического распределения памяти "забывает" о незанятых блоках,  которые могут существовать ниже указателя динамически распределяемой области.  Если вы чередуете обращения к процедурам Маrk и Rеlеаsе с  обращениями  к  процедурам  Dispose и FrееМем, то нужно обеспечить отсутствие таких свободных  блоков.

Переменная FreeList модуля System указывает на  первый  свободный  блок  динамически  распределяемой области памяти.  Данный  блок содержит указатель на следующий свободный блок и  т.д.  Последний  свободный  блок содержит указатель на вершину динамически  распределяемой области (то есть адрес,  заданный  HeapPtr). Если  свободных блоков в списке свободных блоков нет, то FreeList будет  равно HeapPtr.

Формат первых  8 байт  свободного  блока  задается   типом

  TFreeRec:

   type

   PFreeRec = ^TFreeRec;

   TFreeRec = record

   Next: PFreeRec;

   Size: Pointer;

  end;

Поле Next указывает на следующий свободный блок,  или на  ту  же ячейку,  что и HeapPtr, если блок является последним свободным  блоком.  В поле Size записан размер свободного блока.  Значение в  поле Size  представляет собой не обычное 32-битовое значение,  а  "нормализованное" значение-указатель с числом свободных параграфов (16-байтовых  блоков)  в старшем слове и счетчиком свободных  байт (от 0 до 15) в младшем слове.  Следующая  функция BlockSize  преобразует значение поля Size в обычное значение типа Longint:

   function BlockSize(Size: Pointer): Longint;

  type

   PtrRec = record Lo, Hi: Word end;

   begin

   BlockSize := Longint(PtrRec(Size)).Hi)*16+PtrRec(Size).Lo

   end;

Чтобы обеспечить, что в начале свободного блока всегда имеется место для TFreePtr,  подсистема управления динамически распределяемой областью памяти округляет размер каждого блока, выделенного подпрограммами New и GetMem  до 8-байтовой  границы.  Таким  образом,  8  байт выделяется для блоков размером 1..8,  16 байт -  для блоков размером 9..16 и т.д. Сначала это кажется непроизводительной тратой памяти.  Это в самом деле так, если бы каждый блок  был размером 1 байт. Но обычно блоки имеют больший размер, поэтому относительный размер неиспользуемого пространства меньше.

8-байтовый коэффициент  раздробленности  обеспечивает,  что  при большом числе случайного выделения и освобождения блоков  относительно небольшого размера (что типично для записей переменной  длины в программах обработки текста) не приведет к сильной  фрагментации  динамически распределяемой области.  В качестве примера  предположим,  что занимается и освобождается  блок  размером 50  байт.  После  его освобождения  запись о нем включается в список  свободных блоков.  Этот блок округляется до 56 (7*8) байт. Если в  дальнейшем потребуется блок размером от 49 до 56 байт,  то данный  блок будет полностью повторно использован, а не останется от 1 до  7 байт памяти (использование который маловероятно), которые будут  только фрагментировать динамически распределяемую область.

Переменная HeapError позволяет вам реализовать функцию обработки ошибки динамически распределяемой области памяти. Эта функция вызывается каждый раз,  когда программа динамического распределения  памяти  не  может выполнить запрос на выделение памяти.  НеаpЕrror является указателем,  который ссылается на  функцию со  следующим заголовком:

function HeapFunc(Size: Word): Integer; far;

Заметим, что директива far указывает функции обработки ошибки динамически распределяемой области не необходимость  использовать дальнюю модель вызова.

Функция обработки ошибки динамически распределяемой области  реализуется путем присваивания ее адреса переменной НеаpEror:

HeapError := @HeapFunc;

Функция обработки ошибки динамически  распределяемой области памяти получает управление, когда при обращении к процедурам New  или GetМем запрос не может быть выполнен.  Параметр Size содержит  размер блока,  для которого не оказалось области памяти соответствующего размера,  и функция обработки ошибки динамически распределяемой области попытается освободить блок,  размер которого  не  меньше данного размера.

В зависимости от успеха выполнения этой попытки функция  обработки ошибки динамически распределяемой области возвращает значения 0,  1 или 2. Возвращаемое значение 0 свидетельствует о неудачной попытке, что немедленно приводит к возникновению ошибки во  время выполнения программы.  Возвращаемое значение 1 также свидетельствует о неудачной попытке, но вместо ошибки этапа выполнения  оно приводит к тому,  что процедуры GetМем или FrееМем возвращают  указатель nil.  Наконец, возвращаемое значение 2 свидетельствует  об удачной  попытке и вызывает повторную попытку выделить память  (которая также может привести к вызову функции  обработки  ошибки  динамически распределяемой области).

Стандартная обработки функция ошибки динамически распределяемой области всегда возвращает значение 0,  приводя,  таким образом, к ошибке всякий раз, когда не могут быть выполнены процедуры  New или GetМем.  Однако для многих прикладных программ более подходящей  является  простая  функция  обработки ошибки динамически  распределяемой области, пример которой приведен ниже:

function HeapFunc(Size: Word): Integer; far;

begin

   HeapFunc := 1;

end;

Если такая функция реализована,  то вместо  принудительного  завершения  работы программы в ситуации, когда процедуры New или  GetМем не могут выполнить запрос,  она будет  возвращать  пустой  указатель (указатель nil).

Вызов функции  ошибки динамически распределяемой области памяти со значением параметра Size, равным 0, показывает, что удовлетворение запроса на выделение памяти привело к расширению динамически распределяемой области памяти путем  перемещения  HeapPtr  вверх.  Это происходит,  когда в списке свободных блоков нет свободных блоков,  или когда все свободные блоки  слишком малы  для  удовлетворения данного запроса.  Вызов со значением Size,  равным  0, не указывает на состояние ошибки, поскольку между HeapPtr  и  HeapEnd достаточно пространства для расширения,  однако такой вызов служит  предупреждением,  что неиспользуемая  область  выше  HeapPtr сократилась,  и подсистема управления динамически распределяемой областью памяти игнорирует  значение,  возвращаемое  при  вызове такого типа.

В данном разделе поясняется использование память в  программах Borland Pascal для защищенного режима.

Прикладная программа и каждая библиотека в прикладной  программе или  DLL имеет свой собственный сегмент кода.  По умолчанию  модули с аналогичными атрибутами группируются в  сегментах  кода. Вы можете  управлять таким группированием с помощью директив $S и  $G имя_модуля.  Размер одного сегмента кода  не может  превышать  64К, но  общий размер кода ограничен только объемом доступной памяти.

Каждый сегмент кода имеет набор атрибутов,  определяющих по-  ведение сегмента кода при загрузке в память.

Когда сегмент  кода имеет  атрибут MOVEABLE (перемещаемый),  администратор памяти может перемещать сегмент в физической  памяти, чтобы удовлетворить другие запросы распределения памяти. Когда сегмент кода имеет атрибут FIXED (фиксированный),  он  ни при  каких обстоятельствах не перемещается в физической памяти.  Предпочтительным атрибутом является MOVEABLE,  и пока не будет  абсолютно необходимо хранить сегмента в одних и тех же адресах памяти  (это имеет место,  например, для обработчика прерываний), следует  использовать этот  атрибут. Когда  вам потребуется фиксированный  сегмент кода,  такой сегмент кода следует сделать по  возможности  маленьким.

Сегмент кода, имеющий атрибут PRELOAD (предварительно загружаемый), автоматически  загружается при  активизации  прикладной  программы или библиотеки. Атрибут DEMANDLOAD (загружаемый по запросу) откладывает загрузку сегмента или программы до фактического  вызова сегмента.  Хотя это требует больше времени,  но позволяет  прикладной программе экономить память.

Когда сегмент имеет атрибут DIASCARDABLE (выгружаемый),  администратор памяти защищенного режима может освободить занимаемую  сегментом память,  когда требуется дополнительная память. Когда  сегмент имеет атрибут PERMANENT (постоянный),  он все время  хранится в  памяти.  Когда  прикладная программа  вызывает  сегмент  DISCARDABLE, отсутствующий  в памяти,  администратор защищенного  режима сначала загружает его из файла .EXE.  Это  требует  больше  времени, чем если бы сегмент имел атрибут PERMANENT, но позволяет  выполнять прикладную программу в меньшем объеме памяти.

Сегмент DISCARDABLE  в программе DOS защищенного режима аналогичен оверлейному сегменту в программе DOS, в то время как сегмент PERMANENT  в защищенном режиме DOS аналогичен сегменту программы DOS, не являющемуся оверлейным.

Каждая прикладная  программа защищенного режима DOS или библиотека содержит сегмент данных,  которые может иметь  размер до  64К. На сегмент всегда указывает регистр  сегмента  данных (DS).

Этот сегмент содержит типизированные константы и глобальные переменные.

Кроме сегмента данных,  прикладная программа защищенного режима DOS имеет сегмент стека,  который используется для  хранения  локальных переменных,  распределенных процедурами и функциями. На  входе в прикладную программу регистр сегмента стека (SS) и указатель стека (SP) загружены таким образом, что пара регистров SS:SP  указывает  на первый байт после сегмента стека.  Когда вызываются  процедуры и функции,  SP для выделения пространства для  параметров, адреса  возврата  и локальных переменных перемещается вниз.  Когда подпрограмма возвращает управление,  процесс изменяется  на  обратный:  указатель стека увеличивается до значения,  которое он  имел перед вызовом. По умолчанию размер сегмента стека равен 16К,  но с помощью директивы компилятора $M его можно изменить.

В отличие от прикладной программы, DDL DOS защищенного режима не имеет сегмента стека.  Когда в DLL вызывается процедура или  функция, регистр DS изменяется, чтобы указывать на сегмент данных  DLL, но  пара  регистров SS:SP не модифицируется.  Таким образом,  DLL всегда использует сегмент стека вызывающей  прикладной  программы.

Используемые по умолчанию атрибуты сегмента кода - это атрибуты MOVEABLE,  DEMANDLOAD и DISCARDABLE.  Но с помощью директивы  компилятора $C  вы можете задать другие используемые по умолчанию  атрибуты, например:

{$C MOVEABLE PRELOAD PERMANENT}

В прикладной программе защищенного режима DOS нет  необходимости в  администраторе оверлеев. Администратор памяти DOS защищенного режима включает в себя полный  набор  средств  управления  оверлеями, управлять которыми можно через атрибуты сегмента кода.  Описываемые ниже средства доступны для любой программы защищенного режима DOS.

Примечание: Подробности  о  директиве  компилятора  $C  можно найти в Главе 2 ("Директивы компилятора") в "Справочном руководстве программиста".

Расширения 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 "Динамически компонуемые библиотеки".

Переменная HeapLimit  определяет порог  между  маленькими и  большими блоками динамически распределяемой памяти.  По умолчанию  ее значение равно 1024 байтам.  Переменная HeapBlock  определяет  размер, используемый  администратором  памяти  при  распределении  блоков, выделенных для  вторичного распределения.  По  умолчанию  значение HeapBlock  равно 8192 байтам.  Значения этих переменных  изменять не следует, но если вы это сделаете, убедитесь, что значение HeapBlock  составляет не  меньше  четырехкратного  размера  HeapLimit.

Переменная HeapAllocFpals определяет значение флагов атрибутов, передаваемых GlobalAlloc, когда администратор памяти распределяет блоки глобальной памяти.  По умолчанию ее  значение  равно  gmem_Moveable.

Переменная HeapError позволяет вам реализовать функцию обработки ошибки динамически распределяемой области памяти. Эта функция вызывается каждый раз,  когда программа динамического распределения  памяти  не может  выполнить запрос на выделение памяти.  НеаpError является указателем,  который ссылается на  функцию со  следующим заголовком:

function HeapFunc(Size: word): integer; far;

Заметим, что директива far указывает функции обработки ошибки динамически распределяемой области не необходимость  использовать дальнюю модель вызова.

Функция обработки ошибки  динамически распределяемой области  реализуется путем присваивания ее адреса переменной НеаpEror:

HeapError := @HeapFunc;

Функция обработки ошибки динамически распределяемой  области  памяти получает управление,  когда при обращении к процедурам New  или GetМем запрос не может быть выполнен.  Параметр Size содержит  размер блока,  для которого не оказалось области памяти соответствующего размера,  и функция обработки ошибки динамически распределяемой  области  произведет попытку освобождения блока,  размер  которого не меньше данного размера.

Перед вызовом функции обработки ошибки динамически распределяемой области памяти  администратор  динамически распределяемой  памяти пытается выделить свободный блок из блоков вторичного разбиения,  а  также  использовать  непосредственный вызов  функции  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).

В данном разделе поясняется использование памяти в  программах Borland Pascal для Windows.

Каждый сегмент кода имеет набор атрибутов,  определяющих его  поведение при загрузке в память.

Когда сегмент  является перемещаемым  (MOVEABLE),  Windows,  чтобы удовлетворить потребности в  распределяемой  памяти,  может  перемещать сегмент в физической памяти. Когда сегмент кода фиксированный (FIXED),  он не перемещается в физической памяти.  Более  предпочтителен атрибут MOVEABLE,  и если нет абсолютной необходимости хранить сегмент кода по одному и тому же адресу в  физической памяти  (как  бывает в том случае,  если он содержит драйвер  прерываний), следует использовать атрибут MOVEABLE.

Сегмент кода, имеющий атрибут PRELOAD, при активизации прикладной программы или библиотеки загружается автоматически.  Атрибут DEMANDLOAD откладывает загрузку сегмента  до  тех  пор, пока  подпрограмма в сегменте действительно не будет вызвана.

Когда сегмент имеет атрибут DISCARDABLE, Windows при необходимости выделения дополнительной памяти может освобождать память,  занимаемую данным сегментом. Когда прикладная программа обращается к выгружаемому сегменту (DISCARDABLE),  которого нет в памяти,  Windows загружает его сначала из файла .EXE. Это занимает большее  время,  чем если бы сегмент был постоянным (PERMANENT), но позволяет прикладной программе при выполнении занимать меньше места.

Грубо говоря,  сегмент DISCARDABLE  в  прикладной программе  Windows очень напоминает оверлейный сегмент в программе DOS.

По умолчанию сегменту кода  назначаются атрибуты  MOVEABLE,  PRELOAD и PERMANEMT, но с помощью директивы компилятора $C вы можете их изменить. Например:

{$C MOVEABLE DEMANDLOAD DISCARDABLE}

Примечание: Более подробно о директиве $C  рассказывается в Главе 2 ("Директивы компилятора") "Справочного руководства программиста".

В прикладной программе Windows  нет  необходимости  выделять  подсистему управления  оверлеями. Администратор  памяти  Windows  включает  в себя полный набор обслуживающих средств,  управляемых  атрибутами сегмента кода.  Эти средства доступны любой прикладной  программе Windows.

Каждая прикладная программа  или библиотека имеет один сегмент данных,  который называется сегментом локальных динамических  данных и может занимать до 64К. На сегмент локальных динамических  данных  всегда указывает регистр сегмента данных DS.  Он разделен  на четыре части:

Сегмент локальных динамических данных

ЪДДДДДДї

і  і

і Локальная динамически распределя- і

і емая область памяти   і

і  і

ГДДДДДДґ

і     і

і Стек   і

і  і

ГДДДДДДґ

і  і

і Статические данные і

і  і

ГДДДДДДґ

і  і

і  Заголовок задачи і

і  і

АДДДДДДЩ

Рис. 21.7 Сегмент локальных динамических данных.

Первый 16 байт сегмента локальных динамических данных всегда  содержат заголовок задачи,  в котором Windows сохраняет различную  системную информацию.

Область статических данных содержит все глобальные  переменные и типизированные константы,  описанные в прикладной программе  или библиотеке.

Сегмент стека используется для хранения  локальных  переменных, распределяемых процедурами и функциями. На входе в прикладную программу регистр сегмента стека SS и указатель стека SP загружаются таким образом,  что SS:SP указывает на первый байт после  области стека в сегменте локальных динамических данных. При вызове процедур  и функций SP перемещается вниз,  выделяя память для  параметров,  адреса возврата и локальных переменных.  Когда подпрограмма  возвращает управление, процесс изменяется на обратный:  SP увеличивается и принимает то значение,  которое было перед вызовом. Используемый по умолчанию размер области стека в автоматическом сегменте данных равен 8К,  но с помощью директивы компилятора $M это значение можно изменить.

В отличие  от прикладной программы библиотека в сегменте локальных динамических данных не имеет области стека.  При вызове в  динамически  компонуемой библиотеке DLL процедуры или функции регистр DS указывает на сегмент локальных динамических данных  библиотеки,  но  пара регистров SS:SP не изменяется.  Таким образом,  библиотека всегда использует стек вызывающей прикладной  программы.

Последняя часть  в сегменте локальных динамических данных -  локальная динамически распределяемая область.  Она  содержит  все  локальные  динамические данные, которые распределялись с помощью  функции LocalAlloc в Windows.  По умолчанию локальная динамически  распределяемая область имеет размер 8К, но это значение можно изменить с помощью директивы компилятора $M.

Windows допускает, чтобы сегмент локальных динамических данных был  перемещаемым,  но Borland Pascal этого не поддерживает.  Сегмент локальных динамических данных  прикладной  программы или  библиотеки Borland Pascal всегда блокируется,  этим обеспечивается, что селектор (адрес сегмента) сегмента локальных динамических  данных никогда не изменяется. При работе в стандартном или расширенном режиме это не приводит ни к  какому  ухудшению,  поскольку  сегмент сохраняет тот же селектор даже при перемещении в физической памяти.  Однако в реальном режиме, если требуется расширение  локальной динамически распределяемой области,  Windows, возможно,  не сможет этого сделать, поскольку сегмент локальных динамических  данных перемещаться не может.  Если ваша прикладная программа использует локальную динамически распределяемую  область  памяти и  должна выполняться в реальном режиме, то следует обеспечить, чтобы начальный размер локальной динамически распределяемой  области  был таким,  чтобы он удовлетворял всем потребностям в распределении локальной динамической области (для этого используется директива компилятора $M).

Windows поддерживает  динамическое  распределение  памяти  в  двух различных  динамически распределяемых областях:  глобальной  динамически распределяемой области и локальной динамически  распределяемой области.

Примечание: Более  подробно о  локальной и глобальной  динамически распределяемой области рассказывается в  "Руководстве программиста по Windows".

Глобальная динамически  распределяемая область - это пул памяти, доступный для всех прикладных программ. Хотя могут  выделяться блоки глобальной памяти любого размера, глобальная динамически распределяемая  область памяти  предназначена  только для  "больших" областей памяти (256 байт или более).  Каждый блок гло-  бальной памяти имеет избыточный размер 20 байт,  и при  работе в  стандартной  среде Windows в улучшенном режиме 386 существует ограничение в 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 байта.  Изменять эти значения вам незачем,  но если  вы решите это сделать,  убедитесь что HeapBlock имеет значение по  крайней мере в четыре раза превышающее HeapLimit.

Переменная HeapAllocFlags определяет значение флагов атрибутов, передаваемых GlobalAlloc, когда администратор памяти распределяет глобальные  блоки. В  программе по умолчанию используется  значение gmem_Moveable,  а в  библиотеке   -   gmem_Moveable   +  gmem_SSEShure.

Блоки глобальной памяти,  выделяемые администратором динамически распределяемой области памяти,  всегда  блокируются непосредственно  после  своего выделения (с помощью GlobalLock) немедленно после своего выделения и не разблокируются,  пока не  будут  освобождены.  Этим обеспечивается,  что селекторы (адреса сегментов) блоков не изменяются. В стандартной среде Windows и улучшенных режимах процессора 386 фиксированные блоки могут,  тем не менее, перемещаться в физической памяти, освобождая место для других запросов по выделению памяти,  поэтому это не ухудшает производительности администратора динамически  распределяемой  области  памяти Borland Pascal.  Однако в реальном режиме, если от Windows  требуется расширение локальной динамически распределяемой  области, администратор памяти Windows, возможно, не сможет переместить  их,  чтобы выделить другие блоки.  Если ваша прикладная программа  использует  локальную динамически распределяемую область и должна  выполняться в реальном режиме,  можно рассмотреть  при  выделении  блоков  динамической  памяти  возможность использования  средств  распределения памяти, предоставляемых Windows.

Переменная HeapError позволяет вам реализовать функцию обработки ошибки динамически распределяемой области памяти. Эта функция вызывается каждый раз,  когда программа динамического распределения памяти не может выполнить  запрос на  выделение  памяти.  НеаpError  является  указателем,  который ссылается на функцию со  следующим заголовком:

function HeapFunc(Size: word): integer; far;

Заметим, что директива far указывает функции обработки ошибки динамически распределяемой области не необходимость использовать дальнюю модель вызова.

Функция обработки ошибки динамически распределяемой  области  реализуется путем присваивания ее адреса переменной НеаpEror:

HeapError := @HeapFunc;

Функция обработки  ошибки динамически распределяемой области  памяти получает управление,  когда при обращении к процедурам New  или GetМем запрос не может быть выполнен.  Параметр Size содержит  размер блока,  для которого не оказалось области памяти соответствующего размера,  и функция обработки ошибки динамически распределяемой области попытается освободить блок,  размер которого  не  меньше данного размера.

Перед вызовом функции обработки ошибки динамически распределяемой области памяти подсистема динамического распределения  памяти пытается выделить свободный блок из блоков вторичного разбиения, а  также  использовать  непосредственный  вызов   функции  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).

Далее описываются  форматы внутреннего представления данных  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. В противном случае они хранятся,  как слово без знака.

Типы значений  с плавающей  точкой  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 - менее значащий (младшие разряды).

Четырехбайтовое (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.

Десятибайтовое (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.

Строка занимает  столько байт,  какова  максимальная  длина  строки, плюс один байт. Первый байт содержит текущую динамическую  длину строки,  а последующие байты содержат символы строки.  Бит  длины и символы рассматриваются,  как значения без знака.  Максимальная   длина   строки   -  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 обозначает порядковое значение элемента.

Массив хранится в виде непрерывной последовательности  переменных,  каждая из которых имеет тип массива. Элементы с наименьшими индексами хранятся в  младших адресах  памяти.  Многомерный  массив хранится таким образом, что правый индекс возрастает быстрее.

Поля записи хранятся, как непрерывная последовательность переменных.  Первое поле хранится в младших адресах памяти.  Если в  записи  содержатся различные части, то каждая часть начинается с  одного и того же адреса памяти.

Внутренний формат данных объекта имеет сходство с внутренним  форматом записи.  Поля объекта записываются в порядке их описаний  как непрерывная последовательность переменных.  Любое поле, унаследованное от родительского (порождающего) типа, записывается перед новыми полями, определенными в дочернем (порожденном) типе.

Если объектный тип определяет виртуальные методы,  конструктор или деструктор,  то компилятор размещает в объектном типе дополнительное поле данных.  Это 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;

   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.

Второе слово  таблицы виртуальных  методов содержит отрицательный размер экземпляров соответствующего объектного  типа  эта  информация   используется ратификационным  (т.е.  подтверждающим  действительность) механизмом вызова виртуального метода для выявления инициализируемых объектов (экземпляров,  для которых должен  выполняться конструктор) и для проверки  согласованности  таблицы  виртуальных методов. Когда разрешена ратификация виртуального вызова (с помощью директивы {$R+} компилятора,  которая расширена и  включает в себя проверку виртуальных методов), компилятор генерирует вызов программы ратификации таблицы виртуальных методов  перед каждым  вызовом  виртуального метода.  Программа ратификации  таблицы виртуальных методов проверяет,  что первое слово  таблицы  виртуальных методов не равно нулю и что сумма первого  и  второго  слов равна нулю. Если любая из проверок неудачна, то генерируется  ошибка 210 исполняющей системы Borland Pascal.

Разрешение проверок  границ диапазонов  и  проверок вызовов  виртуальных методов замедляет выполнение программы  и  делает ее  несколько  больше,  поэтому используйте {$R+} только во время отладки и переключите эту директиву в состояние {$R-} в окончательной версии программы.

Наконец, начиная  со смещения 4 таблицы виртуальных методов  следует список 32-разрядных указателей методов, один указатель на  каждый  виртуальный  метод в порядке их описаний.  Каждая позиция  содержит адрес точки входа соответствующего  виртуального метода.

На Рис.  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 в поле таблицы виртуальных методов экземпляра C.  Эта автоматическая инициализация является частью кода входа конструктора,  поэтому если управление передается в начало операторной секции, то поле Self таблицы виртуальных методов также будет установлено.  Таким образом,  при возникновении необходимости,  конструктор может выполнить вызов виртуального метода.

Таблица виртуальных  методов объектного  типа  содержит для  каждого описанного в объектном типе  виртуального  метода и  его  предков четырехбайтовую запись.  В тех случаях, когда в порождающих типах (предках) определяется большее число виртуальных  методов, в  процессе создания производных типов может использоваться  достаточно большой объем памяти,  особенно если  создается  много  производных типов. Хотя в производных типах могут переопределяться только некоторые из наследуемых методов,  таблица  виртуальных  методов  каждого  производного типа содержит указатели метода для  всех наследуемых виртуальных методов,  даже если они  не  изменялись.

Динамические методы обеспечивают в таких ситуациях альтернативу. В Borland Pascal имеется формат таблицы методов  и  новый  способ диспетчеризации методов с поздним связыванием.  Вместо кодирования для всех методов объектного типа с поздним связыванием,  в таблице динамических методов кодируются только те методы, которые были в объектном типе переопределены.  Если в наследующих типах переопределяются только некоторые из большого числа методов с  поздним связыванием,  формат таблицы динамических методов использует меньшее пространство,  чем формат таблицы виртуальных методов.

Формат таблицы динамических методов  иллюстрируют  следующие  два объектных типа:

type

TBase = object

X: Integer;

constructor Init;

destructor Done; virtual;

procedure P10; virtual 10;

procedure P20; virtual 20;

procedure P30; virtual 30;

procedure P30; virtual 30;

    end;

 

type

TDerived = object(TBase)

Y: Integer;

constructor Init;

destructor Done; virtual;

procedure P10; virtual 10;

procedure P30; virtual 30;

procedure P50; virtual 50;

end;

На Рис. 21.10 и 21.11 показаны схемы таблицы виртуальных методов и таблицы динамических методов для TBase и TDerived. Каждая  ячейка соответствует слову памяти, а каждая большая ячейка - двум  словам памяти.

ТВМ TBase  ТДМ TBase

ЪДДДДДДДДДДДДДДДДДДї   ЪДДДДДДДДДДДДДДДДДДї

    і 4 і   і 0 і

ГДДДДДДДДДДДДДДДДДДґ   ГДДДДДДДДДДДДДДДДДДґ

і -4   і   і индекс в кэш і

ГДДДДДДДДДДДДДДДДДДґ   ГДДДДДДДДДДДДДДДДДДґ

і Смещ. ТДМ TBase  і  і смещение записи  і

ГДДДДДДДДДДДДДДДДДДґ   ГДДДДДДДДДДДДДДДДДДґ

і 0 і   і 4і

ГДДДДДДДДДДДДДДДДДДґ   ГДДДДДДДДДДДДДДДДДДґ

і   і  і 10   і

і @TBase.Done   і   ГДДДДДДДДДДДДДДДДДДґ

і   і  і 20   і

АДДДДДДДДДДДДДДДДДДЩ   ГДДДДДДДДДДДДДДДДДДґ

  і 30   і

  ГДДДДДДДДДДДДДДДДДДґ

  і 40   і

  ГДДДДДДДДДДДДДДДДДДґ

  і   і

  і @TBase.P10 і

   і   і

  ГДДДДДДДДДДДДДДДДДДґ

  і   і

  і @TBase.P20 і

  і   і

  ГДДДДДДДДДДДДДДДДДДґ

  і   і

  і @TBase.P30 і

  і   і

  ГДДДДДДДДДДДДДДДДДДґ

  і   і

  і @TBase.P40 і

  і   і

  АДДДДДДДДДДДДДДДДДДЩ

Рис. 21.10 Схемы таблицы виртуальных методов и таблицы динамических методов для TBase.

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

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

ТВМ TDerived ТДМ TDerived

ЪДДДДДДДДДДДДДДДДДДДї  ЪДДДДДДДДДДДДДДДДДДї

    і 6 і  і Смещ. ТДМ TBase  і

ГДДДДДДДДДДДДДДДДДДДґ  ГДДДДДДДДДДДДДДДДДДґ

і -6 і  і индекс в кеше і

ГДДДДДДДДДДДДДДДДДДДґ  ГДДДДДДДДДДДДДДДДДДґ

і Смещ. ТДМ TDerivedі  і смещение записи  і

ГДДДДДДДДДДДДДДДДДДДґ  ГДДДДДДДДДДДДДДДДДДґ

і 0  і  і 3 і

ГДДДДДДДДДДДДДДДДДДДґ  ГДДДДДДДДДДДДДДДДДДґ

і і і 10   і

і @TBase.Done і  ГДДДДДДДДДДДДДДДДДДґ

і і і 30   і

АДДДДДДДДДДДДДДДДДДДЩ  ГДДДДДДДДДДДДДДДДДДґ

  і 50   і

  ГДДДДДДДДДДДДДДДДДДґ

  і   і

  і @TDerived.P10 і

  і   і

  ГДДДДДДДДДДДДДДДДДДґ

  і   і

  і @TDerived.P30 і

  і   і

  ГДДДДДДДДДДДДДДДДДДґ

   і   і

  і @TDerived.T50 і

  і   і

  АДДДДДДДДДДДДДДДДДДЩ

Рис. 21.11.  Схемы таблицы виртуальных методов и таблицы динамических методов для TDerived.

Первое слово  таблицы динамических методов содержит смещение  сегмента данных родительской таблицы динамических методов, или 0,  если родительская таблица динамических методов отсутствует.

Второе и третье слово таблицы динамических методов используется в кеш-буфере просмотра динамических методов (см. далее).

Четвертое слово таблицы динамических методов содержит  счетчик записи таблицы динамических методов. Непосредственно за ним  следует список слов, каждое из которых содержит индекс динамического метода,  а затем список соответствующих указателей методов.  Длина каждого списка задается счетчиком записи таблицы динамических методов.

Значения файлового типа представляются в виде записей. Типизированные и  нетипизированные  файлы занимают 128 байт,  которые  располагаются по следующей схеме:

  type

TFileRec = record

Handle  : word; { описатель }

Mode : word;  { режим }

RecSize : word;   { размер записи }

Private : array[1..26] of byte;

UserData   : array[1..16] of byte;

Name : array[0..79] of char;

  end;

Текстовые файлы занимают 256 байт со следующей схемой расположения:

type

   TTextBuf = array[0..127] of char;

   TTextRec = record

Handle  : word;

Mode : word;

    BufSize : word;

Private : word;

BufPos  : word;

BufEnd  : word;

BufPtr  : ^TTextBuf;

OpenFunc   : pointer;

InOutFunc  : pointer;

FlushFunc  : pointer;

CloseFunc  : pointer;

UserData   : array[1..16] of Byte;

    Name: array[0..79] of Char;

Buffer  : TTextBuf;

   end;

В переменной Наndlе содержится номер описателя  файла (когда  файл открыт). Это значение возвращается DOS.

Поле Моdе считается равным одному из следующих значений:

const

fmClosed = $D7B0;

fmInput  = $D7B1;

fmOutput = $D7B2;

fmInOut  = $D7B3;

Значение fmClosed  показывает, что  файл  закрыт. Значения  fmInput и fmOutput показывают, что файл является текстовым файлом  и что для него  была выполнена  процедура  Reset (fmInput)  или  Rewrite (fmOutput).  Значение fmOutput показывает, что переменная  файлового типа  является типизированным или нетипизированным файлом, для которого была выполнена процедура Reset или Rewrite. Любое другое  значение говорит о том,  что для файловой переменной  присваивание не было выполнено (и она,  таким образом, не инициализирована).

Поле UserData  в Borland Pascal недоступно,  и пользовательские программы могут сохранять в нем данные.

Поле Nаме  содержит имя  файла,  которое представляет собой  последовательность  символов, оканчивающуюся  нулевым   символом  (#0).

Для типизированных и нетипизированных полей RесSizе содержит  длину записи в байтах, а поле Рrivate зарезервировано, но является свободным.

Для текстовых  файлов BufPtr  является  указателем на буфер  размером BufSize,  BufPоs представляет  собой индекс  следующего  символа  в  буфере,  который должен быть записан или прочитан,  а  BufEnd  -  счетчик  допустимых символов  в   буфере.  Указатели  OpenFunc,  InOutFunc,  FlushFunc и CloseFunc служат для ссылки на  программы ввода-вывода и используются для  управления  файлом. В  Главе 14 в разделе под заглавием "Драйверы устройств для текстовых файлов" приводится дополнительная информация по этому вопросу.

Процедурные типы хранятся в виде двойного слова.  При этом в  младшем слове содержится смещение процедуры,  а в старшем - базовый сегмент.

В Borland  Pascal реализованы  три предопределенных массива  Mem, MemW и MemL, которые используются для прямого доступа к памяти. Каждый компонент массива Mem представляет собой байт, каждый компонент массива MemW - слово,  а каждый  компонент MemL  -  значение длинного целого типа (Longint).

Для индексирования массива Mem используется специальный синтаксис. Два выражения целочисленного типа Word, разделенные запятыми, используются для задания базового сегмента и смещения ячейки памяти, к которой производится доступ. Например:

Mem[$0040:$0049] := 7;

Data := MemW[Seg(V):Ofs(V)];

MemLong := MemL[64:3*4];

Первый оператор записывает  значение 7  в  байт  по  адресу  $0040:$0049.  Второй оператор помещает значение типа Word,  записанное в первые 2 байта переменной V,  в переменную Data.  Третий  оператор  помещает  значение  типа Longint,  записанное по адресу  $0040:$000C, в переменную MemLong.

Для доступа к портам данных процессора 80х86 Borland  Pascal  реализует два  предопределенных  массива - Port и PortW. Оба эти  массива являются одномерными массивами, где каждый элемент представляет порт данных, адрес которого соответствует индексу. Индекс  имеет тип Word. Элементы массива Port имеют типа Byte, а элементы  массива PortW - Word.

Когда элементами массива Port или PortW присваивается значение, оно выводится в выбранный порт.  Когда на элементы этих массивов имеются ссылки в выражениях, то значение вводится из заданного порта.

Использование массивов Port и PortW ограничено только  присваиванием и ссылками в выражениях, то есть элементы этих массивов  не могут использоваться в качестве  параметров-переменных.  Кроме  того, ссылки  на  весь массив Port или PortW (без индекса) не допускаются.