ЭЛЕКТРОННАЯ БИБЛИОТЕКА КОАПП |
Сборники Художественной, Технической, Справочной, Английской, Нормативной, Исторической, и др. литературы. |
Часть 5 ГЛАВА 15 Внутренняя организация Турбо-Паскаля В этой главе дается дополнительная техническая информа- ция для тех, кто хочет более углубленно изучить программиро- вание на Турбо-Паскале. Мы коснемся таких вопросов, как кар- та памяти, программа динамического распределения памяти, форматы внутренних данных, соглашения по вызовам и т.д. На Рис. 15.1 приведена схема распределения памяти программы на Турбо-Паскале. Префиксный сегмент программы (РSР) - это область длиной 256 байт, которая строится операционной системой ДОС при загрузке файла .ЕХЕ. Адрес сегмента РSР сохраняется в пред- варительно описанной в Турбо-Паскале переменной длиной в слово с именем РrefixSeg. Каждому программному модулю (который включает в себя основную программу и каждый модуль) соответствует сегмент его кода. Основная программа занимает первый сегмент кода. Следующие сегменты кода заняты модулями (в порядке, обратном тому, в котором они указаны в предложении использования). Последний сегмент кода занят библиотекой исполняющей системы (модуль System). Размер отдельного сегмента не может превы- шать 64К, однако общий размер кода ограничен только объемом имеющейся памяти. Сегмент данных (адресуемый через регистр DS) содержит все типизованные константы, за которыми следуют все глобаль- ные переменные. В процессе выполнения программы регистр DS никогда не изменяется. Размер сегмента данных не может пре- вышать 64К. Верхняя граница памяти ДОС ------------------------------------------- ! В списке свободных областей ! ! отмечается имеющееся пространство ! ! динамически распределяемой области ! FreePtr !-----------------------------------------! ! Свободная память ! НеарРtr !-----------------------------------------! ! Динамически распределяемая ! ! область памяти расширяется в ! ! сторону увеличения адресов... ! НеарОrg !-----------------------------------------!OvrHeapEnd ! Оверлейный буфер ! !-----------------------------------------!OvrHeapOrg ! Сегмент стека Стек увеличивается ! ! в сторону уменьшения ! ! адресов... ! !-----------------------------------------!SSeg:SPtr ! Свободная область стека ! !-----------------------------------------!SSeg:0000 ! Глобальные переменные ! !-----------------------------------------!<------- ! Типизованные константы ! ! !-----------------------------------------!DSeg:0000 ! Типизованные константы ! ! !-----------------------------------------! ! ! Сегмент кода модуля System ! ! ! ! ! !-----------------------------------------! ! ! Сегмент кода модуля первого ! Содер-! !-----------------------------------------! жимое ! . (Сегменты кода других модулей) . образа! .-----------------------------------------! файла ! ! Сегмент кода последнего модуля ! .ЕХЕ ! !-----------------------------------------! ! ! Сегмент кода основной программы ! ! !-----------------------------------------! ! ! Префиксный сегмент программы (РSР) ! ! РrefixSeg-------------------------------------------------- Рис. 15.1 При входе в программу регистр сегмента стека (SS) и указатель стека (SР) загружаются так, что SS:SР указывает на первый байт, следующий за сегментом стека. Регистр SS в про- цессе выполнения программы никогда не изменяется, а SР может перемещаться вниз, пока не достигнет нижней границы сегмен- та. Размер сегмента стека не может превышать 64К. По умол- чанию ему назначается размер, равный 16К, но с помощью ди- рективы компилятора $М это значение можно изменить. В динамически распределяемой области сохраняются дина- мические переменные, то есть переменные, выделенные при об- ращениях к стандартным процедурам New и GetMem. Она занимает всю свободную память или часть свободной памяти, оставшуюся при выполнении программы. Действительный размер динамически распределяемой области зависит от максимального и минималь- ного значений, которые можно установить для динамически распределяемой области с помощью директивы компилятора $М. Гарантированный минимальный размер динамически распределяе- мой области не может быть меньше минимального значения, ус- тановленного для этой области. Максимальный размер динами- чески распределяемой области никогда не превышает заданного для нее максимального значения. Если указанный минимальный объем памяти недоступен, то программа не выполняется. По умолчанию минимальный размер динамически распределяемой области равен 0 Кбайт, а макси- мальный размер динамически распределяемой области составляет 1 Мбайт. Это означает, что по умолчанию динамически распре- деляемая область будет занимать всю оставшуюся память. Программа динамического распределения памяти (являющая- ся частью библиотеки исполняющей системы), как можно дога- даться, управляет динамически рапределяемой областью. Де- тально она описывается в следующем разделе. Подсистема управления динамически распределяемой областью памяти Динамически распределяемая область - это похожая на стек структура, которая увеличивается, начиная от младших адресов памяти. При этом используется сегмент динамически распределяемой области. Нижняя граница динамически рапреде- ляемой области запоминается в переменной НеаpОrg, а верхняя граница динамически рапределяемой области соответствует ниж- ней границе свободной памяти и сохраняется в переменной Не- аpРtr. При каждом выделении динамической переменной в дина- мически распределяемой области подсистема динамического распределения памяти (подсистема управления динамически распределяемой областью) перемещает переменную НеарРtr вверх на размер переменной, как бы организуя при этом стек динами- ческих переменных, в котором одна переменная размещается над другой. Переменная НеаpРtr после каждой операции как правило нормализуется, и смещение, таким образом, принимает значения в диапазоне от $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); Схема динамически распределяемой области при этом будет выг- лядеть, как показано на Рис. 15.2. Рtr1 -------------------------------- Младшие ! Содержимое Рtr1 ! адреса Рtr2 !------------------------------! памяти ! Содержимое Рtr2 ! Рtr3 !------------------------------! ! Содержимое Рtr3 ! Рtr4 !------------------------------! ! Содержимое Рtr4 ! Рtr5 !------------------------------! ! Содержимое Рtr5 ! НеаpРtr !------------------------------! ! ! Старшие ! ! адреса ! ! памяти -------------------------------- Рис. 15.6 Метод освобождения областей динамически расп- ределяемой области помощью процедур Маrk и Rеlеаsе Рtr1 -------------------------------- Младшие ! Содержимое Рtr1 ! адреса Рtr2 !------------------------------! памяти ! Содержимое Рtr2 ! НеаpРtr !------------------------------! ! ! ! ! ! ! Старшие ! ! адреса ! ! памяти -------------------------------- Рис. 15.3 Схема динамически распределяемой области при выполнении процедуры Rеleаsе(Р) Оператор Маrk(Р) отмечает состояние динамически распре- деляемой области непосредственно перед выделением памяти для переменной Рtr3 (путем сохранения текущего значения перемен- ной НеаpРtr в Р). Если выполняется оператор Rеleаsе(Р), то схема динамически распределяемой области становится такой, как показано на Рис. 15.3. При этом, поскольку производится обращение к процедуре Маrk, освобождается память, выделенная под все указатели. Примечание: Выполнение процедуры Rеleаsе(НеаpОrg) пол- ностью освобождает динамически распределяемую область памяти, поскольку переменная НеаpОrg указывает на ниж- нюю границу динамически распределяемой области. Рtr1 -------------------------------- Младшие ! Содержимое Рtr1 ! адреса Рtr2 !------------------------------! памяти ! Содержимое Рtr2 ! !------------------------------! !//////////////////////////////! Рtr4 !------------------------------! ! Содержимое Рtr4 ! Рtr5 !------------------------------! ! Содержимое Рtr5 ! НеаpРtr !------------------------------! ! ! Старшие ! ! адреса ! ! памяти -------------------------------- Рис. 15.4 Создание незанятой области ("дыры") в динами- чески распределяемой области памяти Применение процедур Маrk и Rеlеаsе для освобождения па- мяти, выделенной для динамических переменных, на которые ссылаются указатели, в порядке, в точности обратном тому, в котором происходило выделение памяти, весьма эффективно. Од- нако в большинстве программ имеется тенденция в более слу- чайному выделению и освобождению памяти, отведенной для ди- намических переменных, на которые ссылаются указатели, что влечет за собой необходимость использования более тонких методов управления памятью, которые реализованы с помощью процедур Dispose и FrееMem. Эти процедуры позволяют в любой момент освободить память, выделенную для любой динамической переменной, на которую ссылается указатель. Когда с помощью процедур Dispose и FrееМем освобождает- ся память, отведенная для динамической переменной, не являю- щаяся "самой верхней" переменной в динамически распределяе- мой области, то динамически распределяемая область становит- ся фрагментированной. Предположим, что выполнялась та же последовательности операторов, что и в предыдущем примере. Тогда после выполнения процедуры Dispose(Рtr3) в центре ди- намически распределяемой области памяти образуется незанятое пространство ("дыра"). Это показано на Рис. 15.4. Если в данный момент выполняется процедура New(Рtr3), то это опять приведет к выделению той же области памяти. С другой стороны, выполнение процедуры Dispose(Рtr4) увеличит размер свободного блока, так как Рtr3 и Рtr4 были соседними блоками (см. Рис. 15.5). Рtr1 -------------------------------- Младшие ! Содержимое Рtr1 ! адреса Рtr2 !------------------------------! памяти ! Содержимое Рtr2 ! !------------------------------! !//////////////////////////////! !//////////////////////////////! Рtr5 !------------------------------! ! Содержимое Рtr5 ! НеаpРtr !------------------------------! ! ! Старшие ! ! адреса ! ! памяти -------------------------------- Рис. 15.5 Увеличение размера незанятого блока памяти В конечном итоге выполнение процедуры Dispose(Рtr5) приведет сначала к созданию незанятого блока большего разме- ра, а затем НеаpРtr переместится в более младшие адреса па- мяти. Поскольку последним допустимым указателем теперь будет Рtr2 (см. Рис. 15.6), то это приведет к действительному ос- вобождению незанятого блока. Рtr1 -------------------------------- Младшие ! Содержимое Рtr1 ! адреса Рtr2 !------------------------------! памяти ! Содержимое Рtr2 ! НеаpРtr !------------------------------! ! ! ! ! ! ! ! ! ! ! Старшие ! ! адреса ! ! памяти -------------------------------- Рис. 15.7 Освобождение незанятого блока памяти Как показано на Рис. 15.2. динамически распределяемая область памяти теперь находится в том же самом состоянии, в каком она находилась бы после выполнения процедуры Rеlеаsе(Р). Однако создаваемые и освобождаемые при таком процессе незанятые блоки отслеживаются для их возможного повторного использовавания. Список свободных блоков Адреса и размеры свободных блоков, созданных при опера- циях Dispose и FrееМем, хранятся в списке свободных блоков, который увеличивается вниз, начиная со старших адресов памя- ти, в сегменте динамически распределяемой области. Каждый раз перед выделением памяти для динамической переменной, пе- ред тем, как динамически распределяемая область будет расши- рена, проверяется список свободных блоков. Если имеется блок соответствующего размера (то есть размер которого больше или равен требуемому размеру), то он используется. Примечание: Процедура Rеlеаsе всегда очищает список свободных блоков. Таким образом, программа динамичес- кого распределения памяти "забывает" о незанятых бло- ках, которые могут существовать ниже указателя динами- чески распределяемой области. Если вы чередуете обра- щения к процедурам Маrk и Rеlеаsе с обращениями к про- цедурам Dispose и FrееМем, то нужно обеспечить отсут- ствие таких свободных блоков. Указатель списка свободных блоков сохраняется в пере- менной с именем FrееРtr. Хотя эта переменная описывается, как переменная типа указатель, в действительности она указы- вает на массив записей свободных блоков, о чем свидетель- ствует тип переменной FrееListР: type FreeRec = record OrgPtr,EndPtr: pointer; end; FreeList = array[0..8190] of FreeRec; FreeListP = ^FreeList; Поля каждой записи ОrgРtr и ЕndРtr определяют начало и конец каждого свободного блока. (Еnd фактически указывает на первый следующий за блоком свободный байт.) Оба указателя являются нормализованными. Количество записей в массиве FrееList вычисляется по формуле: FreeCount = (8192 - Ofs(FreePtr^) div 8) mod 8192 Это означает, что в списке свободных блоков может со- держаться до 8191 записей. Когда смещение для указателя FreePtr равно 0, то список свободных блоков пуст. Указатель FreePtr можно сравнить с указателем стека в том смысле, что он увеличивается в сторону младших адресов, и все байты, на- чиная от FreePtr и до конца сегмента динамически распределя- емой области являются частью "свободного пространства сте- ка". Примечание: Попытка освободить память, занятую для ди- намической переменной, в ситуации, когда список сво- бодных блоков переполнен, во время выполнения програм- мы приведет в ошибке. Однако переполнение списка сво- бодных блоков - это крайне маловероятная ситуация. Для этого потребуется освободить без повторного использо- вания 8191 полностью несмежных блоков. Указатель FreePrt служит также для отметки верхней гра- ницы свободной памяти в динамически распределяемой области (на нижнюю ее границу указывает НеаpРtr). Заметим, однако, что когда смещение для FreePtr равно 0, то для получения ис- тинного указателя на верхнюю границу динамически распределя- емой области нужно к адресу сегмента добавить $1000. (Факти- чески, адрес сегмента для FrееРtr всегда содержит адрес сег- мента для верхней границы памяти, минус $1000.) При освобождении ряда отмеченных указателями несмежных областей список свободных блоков увеличивается (расширяется вниз), чтобы образовать пространство для записи каждого бло- ка. Пока между указателями НеаpРtr и FrееРtr имеется доста- точное пространство, проблем не возникает. Однако, когда ди- намически распределяемая область почти заполнена, то может обнаружится нехватка пространства для большого списка сво- бодных блоков. При этом во время выполнения программы проис- ходит ошибка. Представим, в частности, что список свободных блоков пуст, а динамически распределяемая область почти заполнена. В такой ситуации обвобождение ряда отмеченных указателями блоков, отличных от того, на который ссылается самый верхний указатель, приведет к расширению блоков для списка свободных блоков. Чтобы предотвратить или предвидеть подобные проблемы, в программе динамического распределения памяти предусмотрена переменная длиной в слово с именем FrееМin, которую можно использовать для управления минимально допустимым размером области памяти между НеаpРtr и FrееРtr. Вы не можете исполь- зовать процедуры New или GetМем для того, чтобы отвести для переменной такую память, которая уменьшила бы размер данной области до значения меньшего, чем FrееМin. Процедуры MemAvail и МахAvail также перед возвращением своих результа- тов вычитают значение FrееМin из размера этой области. Значение, сохраняемое в переменной FrееМin представляет собой размер области в байтах. Для обеспечения пространства для заданного числа записей списка свободных блоков умножьте это число на 8 и сохраните в переменной FrееМin. Заключительное замечание по списку свободных блоков ка- сается потенциальной проблемы фрагментации. Степень детали- зации для программы динамического распределения памяти составляет 1 байт, то есть, если вы отводите для переменной 1 байт, то она будет занимать этот 1 байт. В большинстве случаев, особенно при использовании процедур Маrk и Rеlеаsе, или когда не производится никаких освобождений, это обеспе- чивает оптимальное использование доступной памяти. Однако, это также может оказаться обманчивым. При случайном занятии и освобождении большого числа блоков различного размера, как, например, при работе со строковыми записями в программе обработки текстов, в резуль- тате будет получено очень большое количество небольших неза- нятых блоков, что может привести к переполнению списка сво- бодных блоков. В качестве примера предположим, что занимает- ся и освобождается блок размером 50 байтов. После его осво- бождения запись о нем включается в список свободных блоков. Если в дальнейшем потребуется блок размером 49 байтов, то данный блок будет повторно использован. При этом в списке свободных блоков останется запись о незанятом блоке длиной в 1 байт. Пока не будет освобожден один из соседних блоков (при этом блок длиной в 1 байт сольется в больший блок) ве- роятность использования блока длиной в 1 байт очень мала. Таким образом, эта запись будет находится в списке свободных блоков в течении продолжительного времени, если вообще не до конца работы программы. Если в результате этого возникает переполнение списка свободных блоков, то для округления в сторону увеличения размера, определяемого при каждом вызове процедур GetМем и FrееМем (умножения его на какое-либо число) вы можете ввести "коэффициент раздробленности". В общем случае, чем выше это число, тем менее вероятно возникновение неиспользуемых сво- бодных блоков. Для того, чтобы это сделать, вам потребуется составить свои собственные программы GetМем и FrееМем, кото- рые будут изменять параметр Size и затем обращаться к проце- дурам Systем.GetМем и Systем.FrееМем: procedure MyGetMem(var p: pointer; Size: word) begin Size := Size div 16*16 { наименьший размер - 16 байтов } System.GetMem(p,Size); end; Функция обработки ошибки динамически распределяемой области памяти Реализовать функцию обработки ошибки динамически рас- пределяемой области можнос помощью переменной НеаpЕrror. Эта функция вызывается каждый раз, когда программа динамического распределения памяти не может выполнить запрос на выделение памяти. НеаpЕrror является указателем, который ссылается на функцию со следующим заголовком: {$F+} function HeapFunc(Size: word): integer; {$F-} Заметим, что директива компилятора {$F+} указывает функции обработки ошибки динамически распределяемой области не необходимость использовать дальние обращения. Функция обработи ошибки динамически распределяемой об- ласти реализуется путем присваивания ее адреса переменной НеаpЕror: HeapError := @HeapFunc; Функция обработки ошибки динамически распределяемой об- ласти получает управление, когда при обращении к процедурам New или GetМем запрос не может быть выполнен. Параметр Size содержит размер блока, для которого не оказалось области па- мяти соответствующего размера, и функция обработки ошибки динамически распределяемой области произведет попытку осво- бождения блока, размер которого не меньше данного размера. В зависимости от успеха выполнения этой попытки функция обработки ошибки динамически распределяемой области возвра- щает значения 0, 1 или 2. Возвращаемое значение 0 свидетель- ствует о неудачной попытке, что немедленно приводит в воз- никновению ошибки во время выполнения программы. Возвращае- мое значение 1 также свидетельствует о неудачной попытке, но вместо ошибки во время выполения оно приводит к тому, что процедуры GetМем или FrееМем возвращают указатель nil. Нако- нец, возвращаемое значение 2 свидетельствует об удачной по- пытке и вызывает повторную попытку выделить память (которая также может привести к вызову функции обработи ошибки дина- мически распределяемой области). Станадартная обработки функция ошибки динамически рас- пределяемой области всегда возвращает значение 0, приводя, таким образом, к ошибке всякий раз, когда не могут быть вы- полнены процедуры New или GetМем. Однако для многих приклад- ных задач более подходящей является простая функция обработ- ки ошибки динамически распределяемой области, пример которой приведен ниже: {$F+} function HeapFunc(Size: word) integer; {$F-} begin HeapFunc := 1; end; Если такая функция реализована, то вместо принудитель- ного завершения работы программы в ситуации, когда процедуры New или GetМем не могут выполнить запрос, она будет возвра- щать пустой указатель (указатель nil). Форматы внутреннего представления данных Целый тип Формат, выбираемый для представления переменной целого типа, зависит от ее минимальной и максимальной границ: 1. Если обе границы находятся в диапазоне -128..127 (короткое целое), то переменная хранится, как байт со знаком. 2. Если обе границы находятся в диапазоне 0..255 (бай- товая переменная), то переменая хранится, как байт без знака. 3. Если обе границы находятся в диапазоне -32768..32767 (целое), то переменная хранится, как слово со знаком. 4. Если обе границы находятся в диапазоне 0..65535 (переменная длиной в слово), то переменная хранит- ся, как слово. 5. В противном случае переменная хранится, как двойное слово со знаком (длинное целое). Символьный тип Символьный тип или поддиапазон символьного типа хранит- ся, как байт без знака. Булевский тип Значения и переменные булевского типа запоминаются, как байт. При этом подразумеваются, что они могут принимать зна- чения 0 (Falsе) или 1 (Тruе). Перечислимый тип Значения перечислимого типа хранятся, как байт без зна- ка, если нумерация не превышает 256. В противном случае они хранятся, как слово без знака. Типы с плавающей запятой Типы значений с плавающей запятой (вещественный, с оди- нарной точностью, с двойной точностью, с повышенной точно- стью или сложный) хранятся в виде двоичного представления знака (+ или -), экспоненты и значащей части числа. Предс- тавляемое число имеет значение: +/- значащая_часть Х 2**экспонента где значащая часть числа представляет собой отдельный бит слева от двоичной десятичной запятой (то есть 0 <= значащая часть <= 2). Примечание: В следующей далее схеме слева расположе- ны старшие значащие биты, а справа - младшие значащие биты. Самое левое значение хранится в самых старших адресах. Например, для значения вещественного типа e сохраняется в первом байте, f - в следующих пяти бай- тах, а s - в старшем значащем бите последнего байта. Вещественный тип Шестибайтовое (48-битовое) вещественное число подразде- ляется на три поля: 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. Примечание: Вещественный тип не может использоваться для хранения ненормализованных чисел, а также беско- нечно малых и бесконечно больших значений. Ненормали- зованное число при сохранении его в виде вещественного принимает нулевое значение, а бесконечно малые и бес- конечно большие значения при попытке использовать для их записи формат вещественного числа приводят к ошибке переполнения. Тип числа с одинарной точностью Четырехбайтовое (32-битовое) число подразделяется на три поля: 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. Тип числа с двойной точностью Восьмибайтовое (64-битовое) число подразделяется на три поля: 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-битовое) число подразделяется на че- тыре поля: 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-битовое) число сложного типа подраз- деляется на два поля: 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 и Мах - нижняя и верхняя граница базового типа этого множества. Номер байта для конкретного элемента Е вычисляет- ся по формуле: ByteNumber = (E div 8) - (Min div 8) а номер бита внутри этого байта по формуле: BitNumber = E mod 8 Значения типа массив Массив хранится в виде непрерывной последовательности переменных, каждая из которых имеет тип массива. Элементы с наименьшими индексами хранятся в младших адресах памяти. Многомерный массив хранится таким образом, что правый индекс возрастает быстрее. Значения типа запись Поля записи хранятся, как непрерывная последователь- ность переменных. Первое поле хранится в младших адресах па- мяти. Если в записи содержатся различные части, то каждая часть начинается с одного и того же адреса памяти. Значения файлового типа Значения файлового типа представляются в виде записей. Типизованные и нетипизованные файлы занимают 128 байтов, ко- торые располагаются по следующей схеме: type FileRec = 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 CharBuf = array[0..127] of char; TextRec = 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 : CharBuf; end; В переменной Наndlе содержится номер канала файла (когда файл открыт). Это значение возвращается ДОС. Поле Мо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 представляет собой индекс сле- дующего символа в буфере, который должен быть записан или прочитан, а ВufЕnd - счетчик допустимых символов в буфере. Указатели OpenFunc, InOutFunc, FlushFunc и CloseFunc служат для ссылки на программы ввода-вывода и используются для управления файлом. В одном из следующих далее разделов под заглавием "Драйверы устройств для текстовых файлов" при- водится дополнительная информация по этому вопросу. Процедурные типы Процедурные типы хранятся в виде двойного слова. При этом с младшем слове содержится смещение процедуры, а в старшем - базовый сегмент. Соглашения по вызовам Параметры процедурам и функциям передаются через стек. Перед вызовом процедуры или функции параметры помещаются в стек в порядке их описания. Перед выходом из процедуры или функции все параметры извлекаются из стека. Примерный вызов процедуры или функции можно представить следующим образом: PUSH Param1 PUSH Param2 . . . PUSH ParamX Call ProcOrFunc Параметры могут передаваться по ссылке или по значению. Когда параметр передается по ссылке, то указатель, который ссылается на реальную ячейку памяти, помещается в стек. Ког- да параметр передается по значению, в стек помещается само фактическое значение. Параметры-переменные Параметры-переменные (параметры var) всегда передаются по ссылке, то есть указатель ссылается на ячейку памяти с фактическим значением. Параметры-значения Параметры-значения передаются по значению или по ссыл- ке, в зависимости от их типа и размера. В общем случае, если параметр-значение занимает 1, 2 или 4 байта, то значение по- мещается непосредственно в стек. В противном случае в стек помещается указатель на значение, а процедура или функция копирует затем значение в локальную ячейку памяти. Примечание: В процессоре 8086 не поддерживаются байто- вые инструкции РUSН и РОР, поэтому байтовые параметры всегда передаются в стеке, как слова. Младший байт слова содержит значение, а старший байт слова свободен (и неопределен). Значение или параметр целого типа передается как байт, слово или двойное слово. При этом используется такой же фор- мат, как для представления переменной целого типа. (Для двойных слов старшее слово помещается в стек перед младшим словом, так что младшее слово размещается в более младших адресах.) Параметр символьного типа передается, как байт без зна- ка. Параметр булевского типа передается, как байт со значе- нием 0 или 1. Параметр типа перечисления передается, как байт без знака, если нумерация не превышает 256. В противном случае он передается, как слово без знака. Параметр вещественного типа (вещественное значение) пе- редается, как 6 байтов в стеке, представляя собой, таким об- разом, исключение из того правила, что в непосредственно в стеке передаются только 1, 2 или 4 байта. Параметры тех типов, которые используются в процессоре 8087 (значения с одинарной, двойной или повышенной точностью или сложного типа), не передаются через стек процессора 8086. Вместо этого параметры с типами, которые используются в процессоре 8087, помещаются в порядке их появления во внутренний стек математического сопроцессора 8087. Это огра- ничивает допустимое число параметров процедуры или функции с типами, которые используются в процессоре 8087, значением 8 (стек процессора 8087 имеет восемь уровней глубины). Параметр типа указатель передается в виде двойного сло- ва (адрес сегмента помещается в стек перед смещением, так что часть, представляющая собой смещение, заканчивается в самом младшем адресе). Параметр строкового типа передается в виде указателя на "распакованное" множество длиной 32 байта. Массив или запись из 1, 2 или 4 байтов помещается не- посредственно в стек. Другие массивы и записи передаются, как указатели на значения. Результаты функций Результаты функций перечислимого типа (целые, символь- ные, булевские, типа перечисления) возвращаются в регистрах центрального процессора: байты возвращаются в регистре АL, слова - в регистре АХ, двойные слова - в DХ:АХ (старшее сло- во - в DХ, младшее - в АХ). Результаты функций вещественного типа (значения вещест- венного типа) возвращаются в регистрах DХ:ВХ:АХ (старшее слово - в регистре DХ, среднее слово - в регистре ВХ, млад- шее слово - в АХ). Результаты функции, имеющие один из типов, использую- щихся в процессоре 8087, (значения с одинарной, двойной или повышенной точностью или сложного типа), возвращаются в ре- гистре вершины стека сопроцессора 8087 (SТ(0)). Резльтаты функции типа указатель возвращаются в регист- ре DХ:АХ (адрес сегмента - в DХ, а смещение - в АХ). Что касается результата функции строкового типа, то вы- зывающая программа помещает в стек перед передачей каких-ли- бо параметров временную ячейку памяти, а функция возвращает строковое значение в этой временной ячейке. Функция не должна удалять указатель. Ближние и дальние типы вызовов В центральном процессоре 8086 поддерживается два типа вызовов и инструкций возврата управления - ближние и даль- ние. Ближние вызовы передают управление другой ячейке в пре- делах того же программного сегмента, а дальние вызовы позво- ляют перейти в другой программный сегмент. Инструкция ближнего обращения САLL помещает в стек 16-битовый адрес возврата (только смещение), а инструкция дальнего вызова помещает в стек 32-битовый адрес возврата (адрес сегмента и смещение). Соответсвующая инструкция RЕТ извлекает из стека только смещение или адрес сегмента и сме- щение. На основе описания процедуры в Турбо-Паскале будет ав- томатически выбираться правильный тип обращения. Процедуры, описанные в интерфейстной секции модуля соответствуют даль- нему обращению и могут вызываться из других блоков. Процеду- ры, описанные в программе в секции реализации модуля, явля- ются ближними и могут вызываться только из этой программы или данного модуля. Для некоторых конкретных целей можно потребовать, чтобы процедура имела дальний тип вызова. Например, процедура вы- хода, драйверы устройств для текстовых файлов и другие средства, использующие указатели на процедуры. Директива компилятора {$F+} указывает на необходимость использования дальнего типа вызовов. Процедуры или функции, скомпилирован- ные с данной директивой всегда будут иметь дальний тип вызо- ва. При использовании в Турбо-Паскале директивы {$F-} пра- вильная схема вызова будет выбираться автоматически. По умолчанию назначается режим {$F-}. Вложенные процедуры и функции Процедура или функция считается вложенной, когда она описывается внутри другой процедуры или функции. По умолча- нию вложенные процедуры и функции всегда используют ближний тип вызова (NEAR), поскольку они доступны только внутри оп- ределенной процедуры или функции в том же сегменте кода. Од- нако в оверлейных задачах обычно для того, чтобы обеспечить для всех процедур и функций дальний тип вызова (FAR), ис- пользуется директива {$F+}. При вызове вложенной процедуры или функции компилятор непосредственно перед инструкцией CALL генеритует инструкцию PUSH BP, фактически передавая регистр BP вызывающей програм- мы в качестве дополнительного параметра. После того, как вы- зываемая процедура установит свой собственный регистр BP, регистр ВР вызывающей процедуры доступен, как слово, сохра- ненное в [BP+4] или в [BP+6] (если процедура имеет дальний тип вызова). Используя связб через [BP+4] и [BP+6], вызывае- мая процедура может получить доступ к локальным переменным в границах стека вызывающей процедуры. Следующий пример пока- зывает, как можно получить доступ к локальным переменным из оператора inline во вложенной процедуре: {$F+} procedure PA; var IntA: integer; {$F+} procedure B; var IntB: integer; {$F-} procedure C; var IntC: integer; begin inline( $8B/$46/ |