ЭЛЕКТРОННАЯ БИБЛИОТЕКА КОАПП |
Сборники Художественной, Технической, Справочной, Английской, Нормативной, Исторической, и др. литературы. |
Часть 18 Часть 3. Внутренная организация Турбо Паскаля Глава 16. Использование памяти В данной главе описывается, как программы Турбо Паскаля используют память. Мы рассмотрим схему памяти прикладной программы Турбо Паскаля, внутренние форматы данных, подсистему управления динамически распределяемой областью памяти и прямой доступ к памяти . Сегменты кода Каждый модуль (основная программа или билиотека и каждый модуль) в прикладной программе Турбо Паскаля или DLL имеет собственный сегмент данных. Размер отдельного сегмента кода не может превышать 64К, но общий размер кода ограничивается только доступ ной памятью. Атрибуты сегмента Каждый сегмент кода имеет набор атрибутов, которые определяют поведение блока при загрузке его в память. 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 вы можете их изменить. Например: {$C MOVEABLE DEMANDLOAD DISCARDABLE} Примечание: Более подробно о директиве $C рассказывается в Главе 21 "Директивы компилятора". В прикладной программе Windows нет необходимости выделять подсистему управления оверлеями. Подсистема управления памятью Windows включает в себя полный набор обслуживающих средств, управляемых атрибутами сегмента кода. Эти средства доступны любой пр икладной программе Windows. Сегмент локальных динамических данных Каждая прикладная программа или библиотека имеет один сегмент данных, который называется "сегментом локальных динамических данных" и может занимать до 64К. На сегмент локальных динамических данных всегда указывает регистр сегмента данных DS. Он разд елен на четыре части: Сегмент локальных динамических данных ЪДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДї і і і Локальная динамически распределя- і і емая область памяти і ГДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДґ і і і Стек і і і ГДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДґ і і і Статические данные і і і ГДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДґ і і і Заголовок задачи і і і АДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДЩ Рис. 16.1 Сегмент локальных динамических данных Первый 16 байт сегмента локальных динамических данных всегда содержат заголовок задачи, в котором Windows сохраняет различную системную информацию. Область статических данных содержит все глобальные переменные и типизованные константы, описанные в прикладной программе или библиотеке. Сегмент стека используется для хранения локальных переменных, распределяемых процедурами и функциями. На входе в прикладную программу регистр сегмента стека SS и указатель стека SP загружаются таким образом, что SS:SP указывает на первый байт после области стека в сегменте локальных динамических данных. При вызове процедур и функций SP перемещается вниз, выделяя память для параметров, адреса возврата и локальных переменных. Когда подпрограмма возвращает управление, процесс изменяется на обратный: S P увеличивается и принимает то значение, которое было перед вызовом. Используемый по умолчанию размер области стека в автоматическом сегменте данных равен 8К, но с помощью директивы компилятора $M это значение можно изменить. В отличие от прикладной программы библиотека в сегменте локальных динамических данных не имеет области стека. При вызове в динамически компонуемой библиотеке DLL процедуры или функции регистр DS указывает на сегмент локальных динамических данных биб лиотеки, но пара регистров SS:SP не изменяется. Таким образом, библиотека всегда использует стек вызывающей прикладной программы. Последняя часть в сегменте локальных динамических данных - локальная динамически распределяемая область. Она содержит все локальные динамические данные, которые распределялись с помощью функции LocalAlloc в Windows. По умолчанию локальная динамическ и распределяемая область имеет размер 8К, но это значение можно изменить с помощью директивы компилятора $M. Windows допускает, чтобы сегмент локальных динамических данных был перемещаемым, но Турбо Паскаль для Windows этого не поддерживает. Сегмент локальных динамических данных прикладной программы или библиотеки Турбо Паскаля всегда блокируется, этим обе спечивается, что селектор (адрес сегмента) сегмента локальных динамических данных никогда не изменяется. При работе в стандартном или расширенном режиме это не приводит ни к какому ухудшению, поскольку сегмент сохраняет тот же селектор даже при меремещен ии в физической памяти. Однако в реальном режиме, если от Windows требуется расширение локальной динамически распределяемой области, Windows, возможно, не сможет этого сделать, поскольку сегмент локальных динамических данных перемещаться не может. Если в аша прикладная программа использует локальную динамически распределяемую область памяти и должна выполняться в реальном режиме, то следует обеспечить, чтобы начальный размер локальной динамически распределяемой области был таким, чтобы он удовлетворял вс ем потребностям в распределении локальной динамической области (для этого используется директива компилятора $M). Подсистема управления динамически распределяемой памятью Windows поддерживает динамическое распределение памяти в двух различных динамически распределяемых областях: глобальной динамически распределяемой области и локальной динамически распределяемой области. Примечание: Более подробно о локальной и глобальной динамически распределяемой области рассказывается в "Руководстве программиста по Windows". Глобальная динамически распределяемая область - это пул памяти, доступный для всех прикладных программ. Хотя могут выделяться блоки глобальной памяти любого размера, глобальная динамически распределяемая область памяти предназначена только для "боль ших" областей памяти (256 байт или более). Каждый блок глобальной памяти имеет избыточный размер 20 байт, и при работе в стандартной среде Windows в улучшенном режиме 386 существует ограничение в 8192 блока памяти, только некоторые из которых доступны дл я отдельной прикладной программы. Локальная динамически распределяемая область памяти - это пул памяти, доступной только для вашей прикладной программы или библиотеки. Она расположена в верхней части сегмента данных прикладной программы или библиотеки. Общий размер блоков локальной памяти, которые могут выделяться в локальной динамически распределяемой области, равен 64К, минус размер стека прикладной программы и статических данных. По этой причине локальная динамически распределяемая область памяти лучше подходит для "небольших" б локов памяти (26 байт или менее). По умолчанию размер локальной динамически распределяемой области равен 8К, но с помощью директивы компилятора $M это значение можно изменить. Примечание: Турбо Паскаль для Windows не поддерживает механизм распределения памяти с помощью процедур Mark и Release, которые предусмотрены в версии для DOS. Турбо Паскаль для Windows включает в себя подсистему управления динамически распределяемой памятью, которая реализует стандартные процедуры New, Dispose, GetMem и FreeMem. Для всех выделений памяти подсистема динамически управления распределяемой об ластью памяти использует глобальную динамически распределяемую область. Поскольку глобальная динамически распределяемая область памяти имеет системное ограничение в 8192 блока (что определенно меньше, чем может потребоваться в некоторых прикладных задача х), подсистема управления динамически распределяемой областью памяти Турбо Паскаля для улучшения производительности и обеспечения выделения существенно большего числа блоков включает в себя алгоритм подвыделения сегмента. Примечание: Более подробно об этом рассказывается в Главе 10 "Динамически компонуемые библиотеки". Алгоритм подвыделения сегмента работает следующим образом: при распределении "большого" блока подсистема управления динамически распределяемой областью памяти просто выделяет глобальный блок памяти, используюя подпрограмму Windows ClobalAlloc. При в ыделении "маленького" блока подсистема управления динамически распределяемой областью памяти выделяет больший блок памяти, а затем делит его на более мелкие блоки (как требуется). При выделении "маленьких" блоков перед тем, как подсистема управления дина мически распределяемой областью памяти выделит блок глобальной динамически распределяемой памяти (который будет в свою очередь разбит на блоки), повторно используются все доступные разбитые мелкие блоки. Границу между "маленькими" и "большими" блоками определяется переменной HeapLimit. По умолчанию она имеет значение 1024 байта. Переменная HeapBlock определяет размер, который использует подсистема управления динамически распределяемой областью памят и при выделении блоков для вторичного разбиения. По умолчанию она имеет значение 8192 байта. Изменять эти значения вам незачем, но если вы решите это сделать, убедитесь что HeapBlock имеет значение по крайней мере в четыре раза превышающее HeapLimit. Блоки глобальной памяти, выделяемые подсистемой управления динамически распределяемой областью памяти, всегда являются фиксированными. То есть они всегда выделяются с атрибутом GMEM_FIXED. Этим обеспечивается, что селекторы (адреса сегментов) блоков не изменяются. В стандартной среде Windows и улучшенных режимах процессора 386 фиксированные блоки могут, тем не менее, перемещаться в физической памяти, освобождая место для других запросов по выделению памяти, поэтому это не ухудшает производительност и подсистемы управления динамически распределяемой областью памяти Турбо Паскаля. Однако в реальном режиме, если от Windows требуется расширение локальной динамически распределяемой области, подсистема управления памятью Windows, возможно, не сможет пере местить их, чтобы выделить другие блоки. Если ваша прикладная программа использует локальную динамически распределяемую область и должна выполняться в реальном режиме, можно рассмотреть при выделении блоков динамической памяти возможность использования с редств распределения памяти, предоставляемых Windows. Переменная HeapError Переменная HeapError позволяет вам реализовать функцию обработки ошибки динамически распределяемой области памяти. Эта функция вызывается каждый раз, когда программа динамического распределения памяти не может выполнить запрос на выделение памяти. Н еаpError является указателем, который ссылается на функцию со следующим заголовком: function HeapFunc(Size: word): integer; far; Заметим, что директива far указывает функции обработки ошибки динамически распределяемой области не необходимость использовать дальнюю модель вызова. Функция обработи ошибки динамически распределяемой области реализуется путем присваивания ее адреса переменной НеаpEror: HeapError := @HeapFunc; Функция обработки ошибки динамически распределяемой области памяти получает управление, когда при обращении к процедурам New или GetМем запрос не может быть выполнен. Параметр Size содержит размер блока, для которого не оказалось области памяти соот ветствующего размера, и функция обработки ошибки динамически распределяемой области произведет попытку освобождения блока, размер которого не меньше данного размера. Перед вызовом функции обработки ошибки динамически распределяемой области памяти подсистема динамического распределения памяти пытается выделить свободный блок из блоков вторичного разбиения, а также использовать непосредственный вызов функции Globa lAlloc. В зависимости от успеха выполнения этой попытки функция обработки ошибки динамически распределяемой области возвращает значения 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). Форматы внутреннего представления данных Целочисленные типы Формат, выбираемый для представления переменной целого типа, зависит от ее минимальной и максимальной границ: 1. Если обе границы находятся в диапазоне -128..127 (Shotrint - короткое целое), то переменная хранится, как байт со знаком. 2. Если обе границы находятся в диапазоне 0..255 (Byte - байтовая переменная), то переменая хранится, как байт без знака. 3. Если обе границы находятся в диапазоне -32768..32767 (Integer - целое), то переменная хранится, как слово со знаком. 4. Если обе границы находятся в диапазоне 0..65535 (Word - переменная длиной в слово), то переменная хранится, как слово. 5. В противном случае переменная хранится, как двойное слово со знаком (Longint - длинное целое). Символьный тип Символьный тип Char или поддиапазон (отрезок) символьного типа хранится, как байт без знака. Типы Boolean, WordBool и LongBool Значения и переменные булевского типа Boolean запоминаются, как байт, тип WordBool хранится, как слово (Word), а LongBool - как длинное целое LongInt. При этом подразумеваются, что они могут принимать значения 0 (Falsе) или 1 (Тruе). Примечание: В версии Турбо Паскаля для DOS существовал только тип Boolean, который мог принимать значения 0 (Falsе) или 1 (Truе). Из-за этого отличия программы Турбо Паскаля для Windows могут считывать булевские поля данных из файлов данных Тур бо Паскаля для DOS, но программы Турбо Паскаля для DOS не могут корректно считывать файлы данных Турбо Паскаля для Windows. Перечислимый тип Значения перечислимого типа хранятся, как байт без знака, если нумерация не превышает 256. В противном случае они хранятся, как слово без знака. Типы с плавающей точкой Типы значений с плавающей точкой Real, Single, Double, Extended и Comp (вещественный, с одинарной точностью, с двойной точностью, с повышенной точностью и сложный) хранятся в виде двоичного представления знака (+ или -), показателя степени и значаще й части числа. Представляемое число имеет значение: +/- значащая_часть Х 2^показатель_степени где значащая часть числа представляет собой отдельный бит слева от двоичной десятичной точки (то есть 0 <= значащая часть <= 2). В следующей далее схеме слева расположены старшие значащие биты, а справа - младшие значащие биты. Самое левое значение хранится в самых старших адресах. Например, для значения вещественного типа e сохраняется в первом байте, f - в следующих пяти ба йтах, а s - в старшем значащем бите последнего байта. Вещественный тип Шестибайтовое (48-битовое) вещественное число (Real) подразделяется на три поля: 1 39 8 ЪДДДДВДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДВДДДДДДДДДДДДї і s і f і e і АДДДДБДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДБДДДДДДДДДДДДЩ более значащие биты менее значащие биты Значение v числа определяется с помощью выражений: if 0 < e <= 255, then v = (-1)^s * 2^(e-129)*(l.f). if e = 0, then v = 0. Вещественный тип не может использоваться для хранения ненормализованных чисел, значений, не являющихся числом (NaN), а также бесконечно малых и бесконечно больших значений. Ненормализованное число при сохранении его в виде вещественного принимает ну левое значение, а не числа (NAN), бесконечно малые и бесконечно большие значения при попытке использовать для их записи формат вещественного числа приводят к ошибке переполнения. Тип числа с одинарной точностью Четырехбайтовое (32-битовое) число типа Single подразделяется на три поля: 1 8 23 ЪДДДДВДДДДДДДДДДДДДВДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДї і s і e і f і АДДДДБДДДДДДДДДДДДДБДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДЩ более значащие биты менее значащие биты Значение 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. где NaN означает "не число". Тип числа с двойной точностью Восьмибайтовое (64-битовое) число типа Double подразделяется на три поля: 1 11 52 ЪДДДДВДДДДДДДДДДДДДВДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДї і s і e і f і АДДДДБДДДДДДДДДДДДДБДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДЩ более значащие биты менее значащие биты Значение 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 63 ЪДДДДВДДДДДДДДДДДДДВДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДї і s і e і f і АДДДДБДДДДДДДДДДДДДБДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДЩ более значащие биты менее значащие биты Значение 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 і АДДДДБДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДЩ более значащие биты менее значащие биты Значение 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 Значения типа массив Массив хранится в виде непрерывной последовательности переменных, каждая из которых имеет тип массива. Элементы с наименьшими индексами хранятся в младших адресах памяти. Многомерный массив хранится таким образом, что правый индекс возрастает быстре е. Значения типа запись Поля записи хранятся, как непрерывная последовательность переменных. Первое поле хранится в младших адресах памяти. Если в записи содержатся различные части, то каждая часть начинается с одного и того же адреса памяти. Значения файлового типа Значения файлового типа представляются в виде записей. Типизованные и нетипизованные файлы занимают 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 TCharBuf = array[0..127] of char; TTextRec = record Handle : word; Mode : word; BufSize : word; Private : word; BufPos : word; BufEnd : word; BufPtr : ^CharBuf; OpenFunc : pointer; InOutFunc : pointer; FlushFunc : pointer; CloseFunc : pointer; UserData : array[1..16] of byte; Name : array[0..79] of char; Buffer : TextBuf; end; В переменной Наndlе содержится номер описателя файла (когда файл открыт). Это значение возвращается DOS. Поле Моdе считается равным одному из следующих значений: const fmClosed = $D7B0; fmInput = $D7B1; fmOutput = $D7B2; fmInOut = $D7B3; Значение fmClosed показывает, что файл закрыт. Значения fmInput и fmOutput показывают, что файл является текстовым файлом и что для него была выполнена процедура Reset(fmInput) или Rewrite(fmOutput). Значение fmOutput показывает, что переменная файл ового типа является типизованным или нетипизованным файлом, для которого была выполнена процедура Reset или Rewrite. Любое другое значение говорит о том, что для файловой переменной присваивание не было выполнено (и она, таким образом, не инициализирован а). Поле UserData в Турбо Паскале недоступно и пользовательские программы могут сохранять в нем данные. Поле Nаме содержит имя файла, которое представляет собой последовательность символов, оканчивающуюся нулевым символом (#0). Для типизованных и нетипизованных полей RесSizе содержит длину записи в байтах, а поле Рrivate зарезервировано, но является свободным. Для текстовых файлов ВufPtr является указателем на буфер размером ВufSize, ВufРоs представляет собой индекс следующего символа в буфере, который должен быть записан или прочитан, а ВufEnd - счетчик допустимых символов в буфере. Указатели OpenFunc, I nOutFunc, FlushFunc и CloseFunc служат для ссылки на программы ввода-вывода и используются для управления файлом. В Главе 19 в разделе под заглавием "Драйверы устройств для текстовых файлов" приводится дополнительная информация по этому вопросу. Процедурные типы Процедурные типы хранятся в виде двойного слова. При этом с младшем слове содержится смещение процедуры, а в старшем - базовый сегмент. Прямой доступ к памяти В Турбо Паскале реализованы три предопределенных массива Mem, MemW и MemL, которые используются для прямого доступа к памяти. Каждый компонент массива Mem представляет собой байт, каждый компонент массива MemW - слово, а каждый компонент MemL - знач ение длинного целого типа. Для индексирования массива 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. Примечание: Хотя Турбо Паскаль допускает прямой доступ к памяти, при работе в Windows это не рекомендуется. Чтобы избежать аварийного завершения ваших программ, предоставьте |