



                          ГЛАВА 15

            Внутренняя организация Турбо-Паскаля

     В этой главе дается дополнительная техническая информа-
ция для тех, кто хочет более углубленно изучить программиро-
вание на Турбо-Паскале. Мы коснемся таких вопросов, как кар-
та  памяти,  программа  динамического  распределения памяти,
форматы внутренних данных, соглашения по  вызовам и т.д.  На
Рис. 15.1 приведена схема распределения памяти  программы на
Турбо-Паскале.
     Префиксный сегмент программы (РSР) - это область длиной
256 байт, которая  строится операционной  системой  ДОС  при
загрузке файла .ЕХЕ.  Адрес сегмента РSР сохраняется в пред-
варительно  описанной в  Турбо-Паскале  переменной  длиной в
слово с именем РrefixSeg.
     Каждому   программному  модулю (который включает в себя
основную  программу и  каждый  модуль) соответствует сегмент
его кода.  Основная  программа занимает первый сегмент кода.
Следующие сегменты кода заняты модулями (в порядке, обратном
тому, в  котором  они указаны  в предложении использования).
Последний сегмент кода занят библиотекой исполняющей системы
(модуль System).  Размер отдельного сегмента не может превы-
шать 64К,  однако общий размер кода ограничен только объемом
имеющейся памяти.
     Сегмент данных (адресуемый  через  регистр DS) содержит
все типизованные константы, за которыми следуют все глобаль-
ные переменные. В процессе выполнения  программы  регистр DS
никогда не изменяется. Размер сегмента данных не может  пре-
вышать 64К.

                 Верхняя граница памяти ДОС
        -------------------------------------------
        !       В списке свободных областей       !
        !       отмечается имеющееся пространство !
        !      динамически распределяемой области !
FreePtr !-----------------------------------------!

                                - 2 -


        !-----------------------------------------!
        !       Свободная память                  !
НеарРtr !-----------------------------------------!
        !        Динамически распределяемая       !
        !       область памяти расширяется в      !
        !       сторону увеличения адресов...     !
НеарОrg !-----------------------------------------!OvrHeapEnd
        !            Оверлейный буфер             !
        !-----------------------------------------!OvrHeapOrg
        !  Сегмент стека      Стек увеличивается  !
        !                    в сторону уменьшения !
        !                    адресов...           !
        !-----------------------------------------!SSeg:SPtr
        !      Свободная область стека            !
        !-----------------------------------------!SSeg:0000
        !        Глобальные переменные            !
        !-----------------------------------------!<-------
        !        Типизованные константы           !       !
        !-----------------------------------------!DSeg:0000
        !        Типизованные константы           !       !
        !-----------------------------------------!       !
        !       Сегмент кода модуля System        !       !
        !                                         !       !
        !-----------------------------------------!       !
        !        Сегмент кода модуля первого      ! Содер-!
        !-----------------------------------------! жимое !
        .     (Сегменты кода других модулей)      . образа!
        .-----------------------------------------! файла !
        !       Сегмент кода последнего модуля    !  .ЕХЕ !
        !-----------------------------------------!       !
        !    Сегмент кода основной программы      !       !
        !-----------------------------------------!       !
        !     Префиксный сегмент программы (РSР)  !       !
РrefixSeg--------------------------------------------------

                         Рис. 15.1

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

                                - 3 -


первый байт, следующий за сегментом стека. Регистр SS в про-
цессе выполнения программы никогда не изменяется, а SР может
перемещаться вниз, пока не достигнет  нижней границы сегмен-
та. Размер сегмента стека не может  превышать 64К.  По умол-
чанию ему назначается размер,  равный 16К,  но с помощью ди-
рективы компилятора $М это значение можно изменить.
     В динамически  распределяемой области сохраняются дина-
мические переменные,  то есть переменные, выделенные при об-
ращениях к стандартным процедурам New и GetMem. Она занимает
всю свободную память  или часть свободной памяти, оставшуюся
при выполнении программы.  Действительный размер динамически
распределяемой области  зависит от максимального и минималь-
ного  значений,  которые  можно  установить  для динамически
распределяемой области с помощью  директивы  компилятора $М.
Гарантированный минимальный размер  динамически распределяе-
мой области не может быть меньше  минимального значения, ус-
тановленного для этой  области.  Максимальный размер динами-
чески распределяемой области никогда не превышает  заданного
для нее максимального значения.
     Если указанный  минимальный  объем  памяти  недоступен,
то программа не выполняется. По умолчанию минимальный размер
динамически  распределяемой области равен 0 Кбайт,  а макси-
мальный размер динамически распределяемой области составляет
1 Мбайт.  Это означает, что по умолчанию динамически распре-
деляемая область будет занимать всю оставшуюся память.
     Программа динамического распределения памяти (являющая-
ся частью  библиотеки исполняющей системы),  как можно дога-
даться, управляет динамически  рапределяемой  областью.  Де-
тально она описывается в следующем разделе.

                   Подсистема управления
         динамически распределяемой областью памяти

     Динамически  распределяемая  область  - это  похожая на
стек структура, которая увеличивается,  начиная  от  младших
адресов памяти.  При этом  используется  сегмент динамически
распределяемой области.  Нижняя граница динамически рапреде-
ляемой области  запоминается в переменной НеаpОrg, а верхняя

                                - 4 -


граница динамически рапределяемой области соответствует ниж-
ней границе свободной памяти и сохраняется в  переменной Не-
а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);

Схема динамически распределяемой области при этом будет выг-

                                - 5 -


лядеть, как показано на Рис. 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(Р) отмечает состояние динамически распре-

                                - 6 -


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

                                - 7 -


намических переменных,  на  которые ссылаются указатели, что
влечет за собой  необходимость  использования  более  тонких
методов  управления  памятью,  которые реализованы с помощью
процедур 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 Увеличение размера незанятого блока памяти


                                - 8 -


     В конечном  итоге  выполнение  процедуры  Dispose(Рtr5)
приведет сначала к созданию незанятого блока большего разме-
ра, а затем  НеаpРtr переместится в более младшие адреса па-
мяти. Поскольку последним допустимым указателем теперь будет
Рtr2  (см. Рис. 15.6), то это приведет к действительному ос-
вобождению незанятого блока.

  Рtr1  -------------------------------- Младшие
        ! Содержимое  Рtr1             !  адреса
  Рtr2  !------------------------------!  памяти
        ! Содержимое  Рtr2             !
НеаpРtr !------------------------------!
        !                              !
        !                              !
        !                              !
        !                              !
        !                              ! Старшие
        !                              !  адреса
        !                              !  памяти
        --------------------------------

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

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

                  Список свободных блоков

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

                                - 9 -


рена, проверяется список свободных блоков. Если имеется блок
соответствующего размера (то есть размер которого больше или
равен требуемому размеру), то он используется.

      Примечание: Процедура  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  можно сравнить с указателем стека в том смысле, что

                                - 10 -


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

      Примечание: Попытка освободить память, занятую для ди-
      намической  переменной,  в ситуации, когда список сво-
      бодных блоков переполнен, во время выполнения програм-
      мы приведет в ошибке. Однако переполнение  списка сво-
      бодных блоков - это крайне маловероятная ситуация. Для
      этого потребуется освободить без повторного  использо-
      вания 8191 полностью несмежных блоков.

     Указатель FreePrt служит также для отметки верхней гра-
ницы свободной памяти  в  динамически распределяемой области
(на нижнюю ее  границу  указывает НеаpРtr). Заметим, однако,
что когда смещение для FreePtr равно 0, то для получения ис-
тинного указателя на верхнюю границу динамически распределя-
емой области нужно к адресу сегмента добавить $1000. (Факти-
чески, адрес сегмента для FrееРtr всегда содержит адрес сег-
мента для верхней границы памяти, минус $1000.)
     При освобождении ряда отмеченных  указателями несмежных
областей список свободных блоков  увеличивается (расширяется
вниз), чтобы образовать пространство для записи каждого бло-
ка. Пока между указателями  НеаpРtr и FrееРtr имеется доста-
точное пространство, проблем не возникает. Однако, когда ди-
намически распределяемая  область  почти заполнена, то может
обнаружится нехватка  пространства  для большого списка сво-
бодных блоков. При этом во время выполнения программы проис-
ходит ошибка.
     Представим, в  частности,  что  список свободных блоков
пуст, а динамически распределяемая область  почти заполнена.
В такой ситуации  обвобождение  ряда  отмеченных указателями
блоков, отличных от того, на который ссылается самый верхний
указатель, приведет к расширению блоков для списка свободных
блоков.
     Чтобы предотвратить или предвидеть подобные проблемы, в
программе  динамического  распределения памяти предусмотрена

                                - 11 -


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

                                - 12 -


     Если в  результате  этого возникает переполнение списка
свободных блоков, то  для  округления в  сторону  увеличения
размера, определяемого при каждом вызове  процедур  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;


                                - 13 -


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


                                - 14 -


           Форматы внутреннего представления данных

                         Целый тип

     Формат, выбираемый для  представления переменной целого
типа, зависит от ее минимальной и максимальной границ:

     1.  Если обе границы находятся  в  диапазоне  -128..127
         (короткое целое), то  переменная хранится, как байт
         со знаком.
     2.  Если обе границы находятся в диапазоне 0..255 (бай-
         товая переменная), то  переменая хранится, как байт
         без знака.
     3.  Если   обе    границы    находятся    в   диапазоне
         -32768..32767 (целое), то переменная  хранится, как
         слово со знаком.
     4.  Если обе границы  находятся  в  диапазоне  0..65535
         (переменная длиной  в слово), то переменная хранит-
         ся, как слово.
     5.  В противном случае переменная хранится, как двойное
         слово со знаком (длинное целое).

                       Символьный тип

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

                       Булевский тип

     Значения и переменные булевского типа запоминаются, как
байт. При этом подразумеваются, что они могут принимать зна-
чения 0 (Falsе) или 1 (Тruе).

                      Перечислимый тип

     Значения перечислимого типа хранятся, как байт без зна-
ка, если нумерация не превышает 256.  В противном случае они
хранятся, как слово без знака.


                                - 15 -


                  Типы с плавающей запятой

     Типы значений с плавающей запятой (вещественный, с оди-
нарной точностью, с двойной  точностью,  с повышенной точно-
стью или сложный)  хранятся  в  виде двоичного представления
знака (+ или -),  экспоненты и значащей части числа.  Предс-
тавляемое число имеет значение:

  +/- значащая_часть Х 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.


                                - 16 -


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

              Тип числа с одинарной точностью

     Четырехбайтовое  (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                   !
 ------------------------------------------------------
      старшие биты           младшие биты           порядок

                                - 17 -



     Значение 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                         !
 ------------------------------------------------------
      старшие биты                   младшие биты   порядок

                                - 18 -



     Значение  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 и Мах - нижняя и верхняя граница базового типа этого
множества. Номер байта для конкретного элемента Е вычисляет-

                                - 19 -


ся по формуле:

  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;

                                - 20 -



     Текстовые файлы занимают 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  показы-

                                - 21 -


вает, что переменная файлового  типа  является  типизованным
или нетипизованным файлом, для которого была выполнена  про-
цедура Reset или Rewrite.  Любое  другое  значение говорит о
том, что для файловой переменной присваивание не было выпол-
нено (и она, таким образом, не инициализирована).
     Поле UserData  в  Турбо-Паскале  недоступно и пользова-
тельские программы могут сохранять в нем данные.
     Поле Nаме содержит имя файла, которое  представляет со-
бой последовательность символов, оканчивающуюся нулевым сим-
волом (#0).
     Для типизованных  и нетипизованных полей RесSizе содер-
жит длину  записи  в байтах, а поле Рrivate зарезервировано,
но является свободным.
     Для текстовых  файлов ВufPtr является указателем на бу-
фер размером  ВufSize, ВufРоs представляет собой индекс сле-
дующего  символа  в буфере,  который должен быть записан или
прочитан, а ВufЕnd -  счетчик  допустимых символов в буфере.
Указатели   OpenFunc,   InOutFunc,   FlushFunc  и  CloseFunc
служат для ссылки на программы  ввода-вывода  и используются
для управления  файлом.  В одном из следующих далее разделов
под заглавием "Драйверы устройств для текстовых файлов" при-
водится дополнительная информация по этому вопросу.

                      Процедурные типы

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

                   Соглашения по вызовам

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


                                - 22 -


  PUSH Param1
  PUSH Param2
   .
   .
   .
  PUSH ParamX
  Call ProcOrFunc

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

                    Параметры-переменные

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

                     Параметры-значения

     Параметры-значения  передаются по значению или по ссыл-
ке, в зависимости от их типа и размера. В общем случае, если
параметр-значение занимает 1, 2 или 4 байта, то значение по-
мещается  непосредственно  в стек. В противном случае в стек
помещается указатель  на  значение, а  процедура или функция
копирует затем значение в локальную ячейку памяти.

      Примечание: В процессоре 8086 не поддерживаются байто-
      вые инструкции  РUSН и РОР, поэтому байтовые параметры
      всегда  передаются  в стеке, как  слова.  Младший байт
      слова содержит значение, а старший байт слова свободен
      (и неопределен).

     Значение или параметр целого типа  передается как байт,
слово или двойное слово. При этом используется такой же фор-
мат,  как  для  представления  переменной  целого типа. (Для

                                - 23 -


двойных  слов  старшее слово помещается в стек перед младшим
словом, так что  младшее  слово  размещается в более младших
адресах.)
     Параметр символьного типа передается, как байт без зна-
ка.
     Параметр булевского типа передается, как байт со значе-
нием 0 или 1.
     Параметр  типа  перечисления  передается,  как байт без
знака, если  нумерация не превышает 256.  В противном случае
он передается, как слово без знака.
     Параметр вещественного типа (вещественное значение) пе-
редается, как 6 байтов в стеке, представляя собой, таким об-
разом, исключение из того правила,  что в  непосредственно в
стеке передаются только 1, 2 или 4 байта.
     Параметры тех типов, которые  используются в процессоре
8087 (значения с одинарной, двойной или повышенной точностью
или  сложного  типа),  не  передаются  через стек процессора
8086.  Вместо этого параметры с типами, которые используются
в процессоре 8087,  помещаются в  порядке  их  появления  во
внутренний стек математического сопроцессора 8087. Это огра-
ничивает допустимое число параметров процедуры или функции с
типами, которые  используются в процессоре 8087, значением 8
(стек процессора 8087 имеет восемь уровней глубины).
     Параметр типа указатель передается в виде двойного сло-
ва (адрес сегмента  помещается  в  стек перед смещением, так
что часть, представляющая собой  смещение,  заканчивается  в
самом младшем адресе).
     Параметр строкового типа передается в виде указателя на
"распакованное" множество длиной 32 байта.
     Массив или запись из 1,  2  или 4 байтов помещается не-
посредственно  в  стек.  Другие массивы и записи передаются,
как указатели на значения.

                     Результаты функций

     Результаты  функций перечислимого типа (целые, символь-
ные, булевские, типа  перечисления) возвращаются в регистрах
центрального процессора:  байты  возвращаются в регистре АL,

                                - 24 -


слова - в регистре АХ, двойные слова - в DХ:АХ (старшее сло-
во - в DХ, младшее - в АХ).
     Результаты функций вещественного типа (значения вещест-
венного типа)  возвращаются  в  регистрах  DХ:ВХ:АХ (старшее
слово - в  регистре DХ, среднее слово - в регистре ВХ, млад-
шее слово - в АХ).
     Результаты  функции,  имеющие один из типов, использую-
щихся в процессоре 8087,  (значения с одинарной, двойной или
повышенной точностью или  сложного типа), возвращаются в ре-
гистре вершины стека сопроцессора 8087 (SТ(0)).
     Резльтаты функции типа указатель возвращаются в регист-
ре DХ:АХ (адрес сегмента - в DХ, а смещение - в АХ).
     Что касается результата функции строкового типа, то вы-
зывающая программа помещает в стек перед передачей каких-ли-
бо параметров временную  ячейку памяти, а функция возвращает
строковое  значение  в  этой  временной  ячейке.  Функция не
должна удалять указатель.

               Ближние и дальние типы вызовов

     В центральном процессоре 8086  поддерживается  два типа
вызовов и инструкций  возврата  управления - ближние и даль-
ние. Ближние вызовы передают управление другой ячейке в пре-
делах того же программного сегмента, а дальние вызовы позво-
ляют перейти в другой программный сегмент.
     Инструкция  ближнего  обращения  САLL  помещает  в стек
16-битовый адрес возврата  (только  смещение), а  инструкция
дальнего вызова  помещает  в  стек 32-битовый адрес возврата
(адрес сегмента и смещение).  Соответсвующая  инструкция RЕТ
извлекает из стека только смещение или адрес сегмента и сме-
щение.
     На основе описания процедуры в Турбо-Паскале будет  ав-
томатически выбираться правильный тип обращения.  Процедуры,
описанные в интерфейстной секции модуля  соответствуют даль-
нему обращению и могут вызываться из других блоков. Процеду-
ры, описанные в программе в секции  реализации модуля, явля-
ются ближними и могут вызываться  только из  этой  программы
или данного модуля.

                                - 25 -


     Для некоторых конкретных целей можно потребовать, чтобы
процедура имела дальний тип вызова.  Например, процедура вы-
хода,  драйверы  устройств  для  текстовых  файлов  и другие
средства, использующие  указатели  на  процедуры.  Директива
компилятора {$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+}

                                - 26 -


  procedure B;
  var IntB: integer;
  {$F-}
  procedure C;
  var IntC: integer;
  begin
  inline(
    $8B/$46/<IntC>/      { MOV AX,[BP+IntC]  ;AX = IntC   }
    $8B/$5E/$04/         { MOV BX,[BP+4]     ;BX = стек В }
    $36/$8b/$47/<IntB>/  { MOV AX,SS:[BX+IntB];AX = IntB  }
    $8B/$5E/$04/         { MOV BX,[BP+4]     ;BX = стек B }
    $36/8B/$5F/$06/      { MOV BX,SS:[BX+6]  ;BX = стек А }
    $36/$8B/$47/<IntA>); { MOV AX,SS:[BX+IntA];AX =IntA   }
  end;
  begin end;
  begin end;

           Примечание: Вложенные процедуры и функции  нельзя
      описывать с помощью директивы external.

            Стандартные операторы входа и выхода

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

 PUSH BP              ; сохранить регистр ВР
 MOV BP,SP            ; установить границы стека
 SUB SP,LocalSize     ; выделить память для локальных  пере-
                      ; менных

     В этом примере  LocalSize -  это размер локальных пере-
менных. Инструкция SUВ присутствует  только  в  том  случае,
когда LocalSize не равно нулю. Если тип обращения к процеду-
является  ближним,  то  параметры  начинаются  с ВР+4,  если
длы вызова процедуры  используется дальний тип обращения, то
они начинаются с ВР+6.

                                - 27 -


     Станадартной группой операторов выхода является:

 MOV SP,BP           ; освободить память, выделенную для
                     ; локальных переменных
 POP BP              ; восстановить регистр ВР
 RET ParamSize       ; удалить параметры и выполнить возврат
                     ; управления

     Здесь РаrамSizе - это размер параметров. Инструкция RЕТ
является инструкцией ближнего или дальнего типа,  в  зависи-
мости от типа обращения к процедуре.

             Соглашения по сохранению регистров

     В процедурах и функциях следует сохранять регистры  ВР,
SР, SS и DS. Значения всех других регистров можно изменять.

        Компоновка с программами на языке ассемблера

     С помощью директивы компилятора $L можно выполнить ком-
поновку  программ  или модулей на языке Паскаль и процедур и
функций на языке ассемблера. Из исходного файла на языке ас-
семблера  можно с помощью ассемблера получить объектный файл
(с расширением .ОВJ). Используя редактор  связей,  несколько
объектных  файлов  можно скомпоновать с программой или моду-
лем. При этом используется директива компилятора $L.
     В программе или  модуле на  языке Паскаль процедуры или
функции, написанные на  языке ассемблера, должны быть описа-
ны, как внешние. Например:

 function LoCase(Ch : char): char; external;

     В соответствующем файле на языке  ассемблера все проце-
дуры или функции должны находиться в сегменте с именем СОDЕ,
а имена внешних процедур и функций должны быть указаны в ди-
рективах РUВLIС.  (Вместо  СОDЕ  в  качестве  имени сегмента
воспринимается также Сseg.)

                                - 28 -


     Вы должны обеспечить соответствие процедуры или функции
ее определению  в Паскале.  Это  относится  в типу ее вызова
(ближний или дальний),  числу и  типу  параметров и типу ре-
зультата.
     В исходном файле на  языке ассемблера могут описываться
переменные, содержащиеся  в сегменте с именем DAТA. В исход-
ном файле на языке ассемблера  эти  переменные  являются ло-
кальными и на них  нельзя  ссылаться из модуля или программы
на Паскале.  Они, однако, находятся в том же сегменте, что и
глобальные  переменные Паскаля,  и могут быть доступны через
регистр сегмента DS. (Вместо имени сегмента DAТA может также
восприниматься имя сегмента Dseg.)
     На все процедуры, функции и переменные, описанные в мо-
дуле или программе на Паскале и на те из них, которые описа-
ны в  интерфейсной  секции  используемых модулей, можно ссы-
латься  из исходного файла на языке ассемблера с помощью ди-
рективы ЕХТRN.  При этом  обязанность  обеспечить корректный
тип в определении ЕХТRN также возлагается на вас.
     Когда  объектный  файл указывается в директиве $L, Тур-
бо-Паскаль  преобразует файл  из формата перемещаемых объек-
тных модулей  (.ОВJ)  фирмы  Intel  в свой собственный внут-
ренний формат  перемещаемых модулей. Это преобразование воз-
можно лишь при соблюдении некоторых правил:

     1.  Все процедуры и функции должны быть помещены в сег-
         мент с  именем  СОDЕ,  а  все  локальные переменные
         должны быть помещены в  сегмент  с именем DAТA. Все
         другие  сегменты  игнорируются, поэтому имеется ди-
         ректива  GRОUР. В определениях сегмента может зада-
         ваться  выравнивание на  границу  слова  или  байта
         (WORD  или ВYТЕ).  При  редактировании  связей  они
         всегда  выравниваются на границу слова. В определе-
         ниях  сегментов могут  указываться директивы РUВLIС
         (они игнорируются), но в них не  должно  задаваться
         имя класса. (Вместо имени сегмента СОDЕ может также
         восприниматься имя сегмента Сseg,  а  вместо  имени
         сегмента  DAТA  может  восприниматься  имя сегмента
         Dseg.)

                                - 29 -


     2.  При описании переменных сегменте DAТA  или Dseg для
         определения значения  всегда  используйте  вопроси-
         тельный знак (?). Например:

          Count   DW  ?
          Buffer  DB  128 DUP(?)

              Любой  запрос на  создание  инициализированных
         переменных в сегменте DAТA или Dseg в Турбо-Паскале
         игнорируется.
     3.  При ссылке на процедуры  или  функции типа ЕХТRN не
         указывайте смещение. Недопустима, например, следую-
         щая конструкция:

           EXTRN MyProc : NEAR
           CALL  MyProc + 8

              Заметим, что это ограничение не касается пере-
         менных типа ЕХТRN.
     4.  Байтовые  ссылки на символы типа ЕХТRN недопустимы.
         Это означает,  например,  что операторы НIGНТ и LОW
         нельзя исполльзовать с символами типа ЕХТRN.

              Турбо-Ассемблер и  Турбо-Паскаль

     Турбо-Ассемблер (TASM) значительно облегчает разработку
программ  на языке ассемблера и организации в них интерфейса
с программами на Турбо-Паскале. Турбо-Ассемблер поддерживает
специфическое использование сегментов, схему памяти и языко-
вую   поддержку    для    программистов,    работающих    на
Турбо-Паскале.
     Используя опцию TPASCAL и директиву .MODEL, можно обес-
печить  соблюдение соглашений о связях с Турбо-Паскалем, оп-
ределить имена сегметов, выполнить инструкции PUSH BP и  MOV
PB,SP,  а также обеспечить возврат управления с помощью опе-
раторов POP BP и RET N (где N - это число байтов параметра).
     Директива PROC позволяет вам задать параметры в том  же
порядке,  как  они  определены в программе на Турбо-Паскале.

                                - 30 -


Если вы определяете функцию, которая возвращает строку,  об-
ратите  внимание  на  то,  что  директива  PROC  имеет опцию
RETURN, позволяющую вам получить доступ к временному  указа-
телю строки в стеке и не оказывающую влияния на число байтов
параметра, добавляемых в операторе RET.
     Приведем примеры кода, в корорых используются директивы
.MODEL и PROC:

  .MODEL TPASCAL
  .CODE
 MyProc PROC  FAR 1:BYTE, j:BYTE RETURNS result:DWORD
   PUBLIC MyProc
   les di,result   ;получить адрес временной строки
   mov al,i        ;получить первый параметр i
   mov bl,j        ;получить второй параметр j
     .
     .
     .
   ret

     Определение функции  в  Турбо-Паскале  будет  выглядеть
следующим образом:

     function MyProc(i,j : char) : string external;

     Более  подробную   информацию   об   интерфейсе   между
Турбо-Паскалем  и  Турбо-Ассемблером  можно  найти в Главе 7
"Руководства пользователя по Турбо-Ассемблеру".

            Примеры программ на языке ассемблера

     Следующая программа является примером  модуля и  предс-
тавляет собой две программы  на  ассемблере, предназначенные
для обработки строк.  Функция  UрреrСаsе преобразует символы
строки в  прописные  буквы,  а функция  StringOf  возвращает
строку символов заданной длины.

  unit Strings;

                                - 31 -


  interface
  function UpperCase(S: string): string;
  function StringOf(Ch: char; Count: byte): string;
  inplementation
  {$L STRS}
  function UpperCase; external;
  function StringOf; external;
  end;

     Далее приведен файл на языке ассемблера, в котором реа-
лизованы программы StringOf и  UppеrСаsе.  Перед компиляцией
модуля Strings этот файл должен быть  ассемблирован в файл с
именем STRS.ОВJ.  Обратите внимание на  то, что в программах
используется  дальний тип  вызова, так как они описаны в ин-
терфейсной секции блока.

 CODE SEGMENT BYTE PUBLIC
      ASSUME CS:CODE
      PUBLIC UpperCase, StringOf    ; объявить имена
 function Uppercase(S: string): string
 UpperRes        EQU  DWORD PTR [BP+10]
 UpperStr        EQU  DWORD PTR [BP+6]
 Uppercase       PROC FAR
       PUSH BP            ; сохранить регистр ВР
       MOV  BP,SP         ; установить стек
       PUSH DS            ; сохранить регистр DS
       LDS  SI,UpperStr   ; загрузить адрес строки
       LES  DI,UpperRes   ; загрузить адрес результата
       CLD                ; переместить строку
       LODSB              ; загрузить длину строки
       STOSB              ; скопировать результат
       MOV CL,AL          ; поместить длину строки в СХ
       XOR CH,CH
       JCXZ U3            ; пропустить в случае пустой
                          ; строки
 U1:   LODSB              ; пропустить, если символ отличен
                          ; от 'а'...'z'
       CPM AL,'a'

                                - 32 -


       JB  U2
       CPM AL,'z'
       JA  U2             ; переместить строку
       SUB AL,'a'-'A'     ; преобразовать в прописные буквы
 U2:   STOBS              ; сохранить результат
       LOOP U1            ; цикл по всем символам
 U3:   POP  DS            ; восстановить региср DS
       POP  BP            ; восстановить регистр ВР
       RET  4             ; удалить параметры и возвратить
                          ; управление
 UpperCase   ENDP
 ; function StringOf(Ch: char; Count: byte): string
 StrOfRes        EQU  DWORD PTR [BP+10]
 StrOfChar       EQU  BYTE  PTR [BP+8]
 StrOfCOunt      EQU  BYTE  PTR [BP+6]
  StringOf       PROC FAR
    PUSH BP               ; сохранить региср ВР
    MOV  BP,SP            ; установить границы стека
    LES  DI,StrOfRes      ; загрузить адрес результата
    MOV  AL,StrOfCount    ; загрузить счетчик
    CLD                   ; продвинуться на строку
    STOSB                 ; сохранить длину
    MOV  CL,AL            ; поместить значение счетчика в СХ
    XOR  CH,CH
    MOV  AL,StrOfChar     ; загрузить символ
    REP  STOSB            ; сохранить строку символов
    POP                   ; восстановить ВР
    RET                   ; извлечь параметры и выйти
 SrtingOf    ENDP
 CODE       ENDS
           END

     В  следующем  примере показана программа на ассемблере,
которая  может  ссылаться на программы и переменные Паскаля.
Программа Number считывает до 100 целых значений и затем для
проверки  границ каждого из этих значений вызывает программу
на языке ассемблера.  Если значение выходит за границы, про-
цедура, написанная на ассемблере, вызывает для их распечатки

                                - 33 -


процедуру, написанную на языке Паскаль.

 program Numbers;
 {$L CHECK}
 var
   Data: array[1..100] of integer;
   Count,I: integer;
 procedure RangeError(N: integer);
   begin
     Writeln('Range error: ',N);
   end;
 procedure CheckRange(Min,Max: integer); external;
   begin
     Count := 0;
     while not Eof and (Count < 100) do
     begin
       Count := Count + 1;
       Readln(Data[Count]);
     end;
     CheckRange(-10,10);
   end;

     Файл с программой на ассемблере, реализующий  процедуру
СheckRаngе, приводится далее.  Перед  компиляцией  программы
Numbers его нужно ассебмлировать в файл с  именем СНЕСК.ОВJ.
Заметим, что для процедуры используется ближний тип  вызова,
поскольку это описано в программе.

  DATA SEGMENT WORD PUBLIC
       EXTRN Data: WORD, Count: Word ; переменные Паскаля
  DATA ENDS
  CODE SEGMENT BYTE PUBLIC
       ASSUME CS: CODE, DS: DATA
       EXTRN RangeError: NEAR ; реализовано на Паскале
       PUBLIC CheckRange      ; реализованы здесь
  CheckRange PROC NEAR
     MOV BX,SP               ; получить указатель параметров
     MOV AX,SS:[BX+4]        ; загрузить Мin

                                - 34 -


     MOV DX,SS:[BX+2]        ; загрузить Мах
     XOR BX,BX               ; очистисть индекс данных
     MOV CX,Count            ; загрузить счетчик
     JCXZ SD4                ; пропустить если равно 0
SD1: CMP Data[Bx],AX         ; слишком мало?;
     JL  SD2                 ; да, перейти
     CMP Data[BX],DX         ; слишком велико?
     JLE SD3                 ; нет, перейти
SD2: PUSH AX                 ; сохранить регистры
     PUSH BX
     PUSH CX
     PUSH DX
     PUSH Data[BX]           ; передать выходящее за границы

                             ; значение в Паскаль
     CALL RangeError         ; вызвать процедуру на Паскале
     POP DX                  ; восстановить регистры
     POP CX
     POP BX
     POP AX
SD3: INC BX                  ; установить указатель на
 следу-
                             ; ющий элемент
     INC BX
     LOOP SD1                ; повторить цикл для каждого
                             ; элемента
SD4: RET 4                   ; очистить стек и возвратить
                             ; управление
CheckRange ENDS
           END

                 Пример на Турбо-Ассемблере

     Приведем пример программы на Турбо-Ассемблере, в  кото-
ром  используются  все преимущества поддержки в TASM средств
связи с Турбо-Паскалем. Этот  пример  являетмя  модификацией
предыдущей программы.


                                - 35 -


  .MODEL TPASCAL               ; модель кода Турбо-Паскаля
  LOCALS @@                    ; определить локальный
                               ; префикс меток
  .DATA                        ; сегмент данных
  EXTRN Buffer:WORD,COunt:WORD ; перменные Паскаля
  .CODE                        ; сегмент кода
        EXTRN RangeError: NEAR ; реализовано на Паскале
        PUBLIC CheckRange      ; реализованы здесь
  CheckRange PROC NEAR
      MOV BX,SP                ; получить указатель
 параметров
      MOV AX,SS:[BX+4]         ; загрузить Мin
      MOV DX,SS:[BX+2]         ; загрузить Мах
      XOR BX,BX                ; очистисть индекс данных
      MOV CX,Count             ; загрузить счетчик
      JCXZ SD4                 ; пропустить если равно 0
@@1:  CMP Data[Bx],AX          ; слишком мало?;
      JL  SD2                  ; да, перейти
      CMP Data[BX],DX          ; слишком велико?
      JLE SD3                  ; нет, перейти
@@2:  PUSH AX                  ; сохранить регистры
      PUSH BX
      PUSH CX
      PUSH DX
      PUSH Data[BX]            ; передать выходящее за гра-
                               ; ницы значение в Паскаль
      CALL RangeError          ; вызвать процедуру на Паскале
      POP DX                   ; восстановить регистры
      POP CX
      POP BX
      POP AX
@@3:  INC BX                   ; установить указатель на сле-
                               ; дующий элемент
      INC BX
      LOOP SD1                 ; повторить цикл для каждого
                               ; элемента

                                - 36 -


@@4:  RET                      ; очистить стек и возвратить
                               ; управление
CheckRange ENDS
           END

     Заметим, что при использовании директивы .MODEL TPASCAL
Турбо-Ассемблер  будет автоматически генерировать перед пер-
вой инструкцией стандартные операторы  входа  и  стандартные
операторы выхода после инструкции RET.

                  Внутренний машинный код

     Для небольших  подпрограмм  на  языке  ассемблера очень
удобно  использовать  внутренние  директивы и операторы Тур-
бо-Паскаля (операторы inline). Они позволяют вставлять инст-
рукции машинного кода непосредственно в программу или  текст
блока, вместо того, чтобы использовать объектный файл.

                      Операторы Inline

     Оператор  inline  состоит  из  зарезервированного слова
Inline, за  которым  следует один или более внутренних запи-
сей  (записей машинного кода),  разделенных  косой  чертой и
заключенных в круглые скобки:

 inline(10/$2345/Count+1/Data-Offset);

     Оператор inline имеет следующий синтаксис:

               --------    ---    --------------    ---
 Оператор  -->( inline )->( ( )-->! запись во  !-->( ) )-->
  inline       --------    ---  ^ ! внутреннем ! !  ---
                                ! ! машинном   ! !
                                ! ! коде       ! !
                                ! -------------- !
                                !                !
                                !       ---      !
                                -------( / )<-----
                                        ---

                                - 37 -



     Каждый оператор inline состоит из необязательного  спе-
цификатора размера, < или > и  константы  или идентификатора
переменой, за которой следуют  ноль или более спецификаторов
смещения (см. описанный далее синтаксис).  Спецификатор сме-
щения  состоит  из +  или -, за  которым  следует константа.
     Каждая внутренняя запись порождает 1 байт или одно сло-
во кода.  Значения  вычисляется,  исходя  из значения первой
константы или смещения идентификатора переменной, к которому
добавляется или из  которого  вычитается  значение каждой из
последующих констант.

                                   -------------
 Запись во ----------------------->! константа !---------->
 внутреннем  !    ---       ^      -------------       ^
 машинном    !-->( < )------!                          !
 коде        !    ---       !                          !
             !    ---       !                          !
             !-->( > )-------                          !
             !    ---                                  !
             !  -----------------                      !
             -->! идентификатор !-----------------------
                !  переменной   ! !                  ^
                ----------------- !                  !
                             ------                  -------
                             !      ------   -----------   !
                             ------>!знак!-->!константа!----
                                ^   ------   -----------  !
                                !                         !
                                ---------------------------

     Если запись в машинном коде состоит только из  констант
и  если ее значение лежит в 8-битовом диапазоне (0..255), то
она порождает один байт кода. Если значение выходит за  гра-
ницу  8-битового диапазона или если внутренняя запись ссыла-
ется на переменную, то генерируется одно слово кода (младший

                                - 38 -


значащий байт следует первым).
     Операторы < и > могут использоваться для отмены автома-
тического  выбора  размера, который был описан  ранее.  Если
оператор  inline  начинается  с  оператора <, то в код вклю-
чается только младший значаший байт значения,  даже если это
16-битовое  значение.   Если  оператор  inline  начинается с
оператора >, то в код  включается  всегда  слово,  даже если
старший значащий байт равен 0. Например, оператор:

  inline(<$1234/>$44);

гененирует код длиной три байта: $34,$44,$00.
     Значение идентификатора переменной во внутренней записи
представляет собой адрес смещения переменной внутри ее базо-
вого сегмента. Базовый сегмент глобальных  переменных (пере-
менных, описанных на самом внешнем уровне в модуле или прог-
рамме) и типизованные константы, доступ к  которым организо-
ван через регистр DS, представляют собой сегмент данных. Ба-
зовый сегмент  локальных  переменных  (переменных, описанных
внутри подпрограммы) явяется сегментом стека.  В этом случае
смещение переменной относится в  регистру  ВР, что автомати-
чески влечет за собой выбор сегмента стека.

      Примечание: Регистры ВР, SР, SS и DS должны сохранять-
      ся с помощью внутренних операторов. Значение всех дру-
      гих регистров можно изменять.

     В  следующем  примере  оператора  inline   генерируется
машинный код для  записи  заданного  числа слов или данных в
указанную переменную.  При вызове  процедуры  FillWord Count
слов со значением Data записывается в памяти, начиная с пер-
вого байта, обозначенного как Dest.

   procedure FillWord(var Dest, Count, Data: word);
   begin
     inline(
       $C4/$BE/Dest/    { LES DI,Dest[BP]  }
       $8B/$8e/Count/   { MOV CX,Xount[BP] }

                                - 39 -


       $8B/$86/Data/    { MOV AX,Data[BP]  }
       $FC/             { CLD              }
       $F3/$AB);        { REP STOSW        }

     В операторной части блока операторы  inline могут  сво-
бодно чередоваться с другими операторами.

                    Директивы inline

     Директивы inline позволяют писать процедуры и  функции,
которые преобразуются  при  каждом вызове в  заданную после-
довательность инструкций, представляющих собой машинный код.
Синтаксис у директивы  inline такой  же,  как  у  оператора
inline:

                                  --------------
 Директива ---------------------->!  оператор  !-->
  inline                          !  inline    !
                                  --------------

     При вызове  обычной  процедуры или функции (включая те,
которые содержат в себе операторы inline) комилятором  гене-
рируется такой код,  в  котором  параметры  (если они имеют-
ся) помещаются в стек, а затем уже для обращения к процедуре
или функции  генерируется  инструкция САLL. Однако, когда вы
обращаетесь к процедуре или функции типа inline,  компилятор
вместо  инструкции  САLL генерирует код из директивы inline.
Вот короткий пример двух директив inline:

     procedure DisableInterrupts; inline($FA); { CLI }
     procedure EnableInterrupts; inline($FB); { STI }

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

                                - 40 -


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

  function LongMul(X,Y : integer): longint;
    inline(
      $58/              { POP DS ; извлечь из стека Y }
      $5A/              { POP AX ; извлечь из стека X }
      $F7/$EA);         { IMUL DX ; DX:AX = X*Y }

     Обратите внимание на отсутствие инструкций  входа и вы-
хода и инструкции  возврата  управления.  Их  присутствия не
требуется,  поскольку при вызове этой функции содержащиеся в
ней четыре байта просто включаются в текст программы.
     Директивы inline предназначены только для очень  корот-
ких (менее 10 байтов) процедур и функций.
     Из-за того, что процедуры и функции типа  inline  имеют
характер макроопределений, они не могут использоваться в ка-
честве аргумента оператора @ или в  функциях  Addr,  Оffs  и
Seg.

              Прямая память и доступ к портам

                  Массивы Мем, МемW и МемL

     В Турбо-Паскале реализованы три предопределенных масси-
ва - Мем, МемW и МемL. Эти массивы  используются для прямого
доступа к  памяти.  Каждый  элемент массива Мем представляет
собой байт,  каждый  элемент МемW - это слово, а каждый эле-
мент массива МемL является значением длинного целого типа.
     Для индексов массивов Мем используется специальный син-
таксис: для того,  чтобы  задать  базовый сегмент и смещения
для ячейки памяти, к которой  производится доступ, использу-
ются два  выражения  целого типа длиной в слово, разделенные
двоеточием. Приведем несколько примеров:

                                - 41 -



  Mem[$0040 : $0049] := 7;
  Data := Mem[Seg(V) : Ofs(V)];
  MemLong := MemL[64 : 3*4];

     Первый   оператор   сохраняет   значение   7   в  байте
$0040:$0049. Второй оператор помещает значение длиной в сло-
во, записанное в первых двух байтах переменной V, в перемен-
ную Data.  Третий оператор помещает значение длинного целого
типа, записанное по адресу $0040:$000С в переменную МемLong.

                    Массивы Роrt и РоrtW

     Для доступа  к  портам  данных  центрального процессора
80х86 в Турбо-Паскале реализованы два  предопределенных мас-
сива - Роrt и РоrtW. Оба массива являются одномерными и каж-
дый элемент представляет собой порт  данных, для которых ад-
рес порта соответствует индексу.  Индексом является значение
целого типа длиной в слово. Элементы массива Роrt имеют дли-
ну один байт, а элементы массива РоrtW имеют длину одно сло-
     Когда элементу массива  Роrt   или  РоrtW присваивается
значение, то это значение представляет собой вывод в выбран-
ный порт. Когда элементы массива  Роrt  или  РоrtW  являются
ссылками на выражение, его  значение представляет собой ввод
из выбранного порта. Приведем несколько примеров:

 Port[$20] := $20;
 Port[Base] := Port[Base] xor Mask;
 while Port[$B2] and $80 = 0 do         { ожидание }

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


                                - 42 -


                    Обработка прерываний

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

          Разработка процедур обработки прерываний

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

 procedure IntHandler(Flags,CS,IPAX,BX,CX,DX,SI,DI,DS,ES,BP:

                       word);
  interrupt;
   begin
     .
     .
     .
   end;

     Как  можно  видеть,  все регистры передаются в качестве
псевдо-параметров, так что вы можете их использовать и изме-
нять в своей программе.  Вы можете опустить некоторые из па-
раметров или все  параметры, начиная с параметра Flag и кон-
чая ВР.  Попытка  описать  большее количество параметров или
попытка опустить отдельный  параметр без пропуска также того
параметра, за которым он следует, является ошибкой, хотя со-
общения о ней не выдается. Например:

 procedure IntHandler(DI,ES,BP : word);
 procedure IntHandler(SI,DI,DS,ES,BP : word);

     При входе в нее процедура обработки  прерываний автома-
тически сохраняет все регистры (независимо от заголовка про-

                                - 43 -


цедуры) и инициализирует регистр DS:

 PUSH AX
 PUSH BX
 PUSH DX
 PUSH SI
 PUSH DI
 PUSH DS
 PUSH ES
 PUSH BP
 MOV  BP,SP
 SUB  SP,LocalSize
 MOV  AX,SEG DATA
 MOV  DS,AX

     Обратите внимание на  отсутствие  процедуры  СLI, чтобы
разрешить дальнейшие прерывания.  С помощью оператора inline
вы можете написать ее сами (если это необходимо). Набор опе-
раторов  выхода  восстанавливает регистры и выполняет  функ-
цию возврата прерывания:

  MOV SP,BP
  POP BP
  POP ES
  POP DS
  POP DI
  POP SI
  POP DX
  POP CX
  POP BX
  POP AX
  IRET

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

                                - 44 -


честве пользовательского сервисного  средства,  аналогичного
вызову функции ДОС по инструкции INТ 21Н.
     В процедурах обработки прерываний, обслуживающих преры-
вания, получаемые от аппаратных схем, следует воздерживаться
от использования  каких-либо  программ  ввода-вывода  Турбо-
Паскаля или программ распределения памяти, поскольку они  не
являются  реентерабельными.   Из-за  их  нереентерабельности
нельзя также использовать никакие функции ДОС.

          Драйверы устройств для текстовых файлов

     Как уже упоминалось в Главе 10 ("Ввод и  вывод"),  Тур-
бо-Паскаль позволяет вам определить ваши собственные драйве-
ры устройств для текстовых файлов.  Драйвер  устройства  для
текстовых  файлов  представляет собой набор из четырех функ-
ций, представляющих собой полный  интерфейс  между  файловой
системой  Турбо-Паскаля  и каким-либо устройством. Этими че-
тырьмя функциями, с помощью которых определяется любой драй-
вер устройства, являются функции:

 Open, InOut, Flush, Close

     Заголовок функции для каждой  функции  имеет  следующий
вид:

  function DeviceFunc(var F: TextRec) integer

где ТехtRес - тип записи текстового файла, который определя-
ется в предыдущем разделе под заглавием "Типы файлов". Чтобы
в функции использовался  дальний  тип  вызова, каждая из них
должна компилироваться с директивой {$F+}. Значение, возвра-
щаемое  каждой  фукнцией,  представляющей  собой интерфейс с
устройством,  становится  значением,  возвращаемым  функцией
IOResult. Возвращаемое значение 0 свидетельствует  об успеш-
ном завершении операции.

      Примечание: Хотя ТехtRec является в стандартном модуле
      Dos внутренней  записью,  вы можете  переопределить ее

                                - 45 -


      (аккуратно!), как это будет делаться  далее.  Наважно,
      какие имена используются для  функций,  осуществляющих
      интерфейс с  устройством,  но  параметр каждой функции
      должен быть параметром-переменной и иметь тип ТехtRec,
      а результат функции должен быть целого типа.

     Для того, чтобы  связать функцию, осуществляющую интер-
фейс с устройством, с конкретным файлом, нужно написать спе-
циальную процедуру Assign (аналогичную процедуре AssignCrt в
модуле Crt). Эта процедура должна присваивать адреса четырех
функций,  осуществляющих  интерфейс  с устройствами, четырем
указателям на функции в переменной текстового файла.  В при-
дачу   к  этому  вы  должны  сохранить  системную  константу
fmClosed в поле Моdе, записать размер буфера текстового фай-
ла в переменую ВufSize, сохранить указатель буфера текстово-
го файла в переменной BufPtr и очистить строку Nаме.
     Предположим, например, что именами четырех функций, ре-
ализующих интерфейс с устройством, являются:

 DevOpen, DevInOut, DevFlush, DevClose, Assign

Тогда процедура Assing может выглядеть следующим образом:

 procedure AssignDev(var F: Text);
 begin
   with TextRec(F) do
   begin
     mode      := fmClosed;
     BufSize   := SizeOf(Buffer);
     BufPtr    := @Buffer;
     OpenFunc  := @DevOpen;
     InOutFunc := @DevInOut;
     FlushFunc := @DevFlush;
     CloseFunc := @DevClose;
     Name[0]   := #0;
  end;
 end;


                                - 46 -


     Для  хранения  пользовательской  информации  в функции,
реализующие интерфейс с  устройством,  может  использоваться
поле записи UserData. Это поле не изменяется файловой систе-
мой Турбо-Паскаля.

                        Функция Ореn

     Функция Ореn вызывается стандартными процедурами Rеset,
Rеwritе и Appеnd для открытия текстового файла, связанного с
устройством. Чтобы отметить  была ли функция Ореn вызвана из
процедуры Rеset, Rеwritе или Appеnd,  на входе поле Моdе со-
держит значение fmInput, fmOutput или fmInOut.
     В соответствии со значением Моdе функция Ореn подготав-
ливает файл для ввода  или  вывода.  Если в Моdе указывается
FmInOut (указывая, что функция Оpеn была вызвана из Арреnd),
то перед возвратом  управления  функцией  Оpеn  это значение
должно быть изменено на fmOutput.
     Функция Opеn всегда вызывается перед любой другой функ-
цией,  реализующей интерфейс с устройством.  По этой причине
функция Аssign инициализирует только поле ОpеnFunc, отклады-
вая инициализацию оставшихся векторов до завершения выполне-
ния функции Оpеn. Основываясь на значении  поля Моdе функция
Оpеn может  установить указатели как для фукнций, ориентиро-
ванных на ввод, так и для функций, ориентированных на вывод.
Это  позволяет избежать  определения текущего режима в функ-
циях InOut, Flush и Close.

                       Функция InOut

     Всякий раз, когда требуется ввод с устройства или вывод
на него, функциями  Readln, Read, Write, Writeln, Page, Eof,
SeekEof, SeekEoln и Close вызывается функция InOut.
     Когда в поле Моdе установлено значение fnInput, функция
InOut  считывает  символы (объем  ввода  задается переменной
BufSize) в BufPtr^  и возвращает число считанных символов  в
BufEnd, а также записывает  0 в ВufРоz. Если функция InOut в
результате запроса на  ввод  возвращает в ВufЕnd значение 0,

                                - 47 -


то переменная Еоf для файла принимает значение Тruе.
     Когда в поле  Моdе установлено значение fnOutput, функ-
ция InOut записывает символы, количество которых определяет-
ся переменной ВufРоs, из ВufPtr^  и возвращает в ВufРоs зна-
чение 0.
                        Функция Flush

     Функция Flush  вызывается  в  конце  выполнения  каждой
функции Rеаd, Write, Rеаdln или  Writeln.  Она  может  также
сбрасывать буфер текстового файла. Если в поле Моdе находит-
ся fmInput, функция Flush для того, чтобы отбросить оставши-
еся  (несчитанные)  символы  в  буфере,  может  записать 0 в
BufPos и BufEnd. Это средство используется редко.
     Если в поле Моdе находится fnOutput,  то  функция Flush
может записать содержимое буфера, в  точности таким же обра-
зом, как функция  InOut.  Это гарантирует, что выведенный на
устройсто текст  появится  на  устройстве  немедленно.  Если
функция Flush не  выполняет никаких действий, текст не будет
выведен на устройство,  пока буфер не станет полным или файл
не будет закрыт.

                       Функция Сlоsе

     Функция Сlоsе  вызывается  стандартной процедурой Сlоsе
для  закрытия  связанного  с  устройством  текстового файла.
(Процедуры Rеsеt,  Rеwritе, Appеnd  также  вызывают  функцию
Сlоsе, если  файл,  который  они открывают, уже был открыт.)
Если в поле Моdе  находится  fmOut, то перед вызовом функции
Сlоsе  файловая  система  Турбо-Паскаля обращается к функции
InOut. Это гарантирует вывод на устройство всех символов.

      Примеры драйверов устройств для текстовых файлов

     Приведенный  далее  модуль  представляет  собой драйвер
коммуникационных портов (последовательных портов) компьютера
IMB PC для текстового файла:

 unit AuxInOut;

                                - 48 -



 interface
 uses Dos;

 procedure AssignAux(var F: Text; Port, Params: word);

 implementation

 {$K-,R-}

 const
  fmClose  := $D7B0;
  fmInput  := $D7B1;
  fmOutput := $D7B2;
  fmInOut  := $D7B3;

 type
   CharBuf = atrray[0..127] of char;

 procedure AuxInit(Port, Params : word);
 inline(
   $58/          { POP  AX  ; извлечь из стека параметры }
   $5A/          { POP  DX  ; извлечь из стека номер порта }
   $B4/$00/      { MOV AH,0 ; инструкция инициализации }
   $CD/$14);     { INT 14H  ; обращение к базовой системе
                            ;  ввода-вывода }
 function AuxInchar(Port: word): char;
 inline(
   $5A/          { POP AX   ; извлечь из стека номер порта }
   $B4/$02/      { MOV AH,2 ; инструкция ввода }
   $CD/$14/);    { INT 14H  ; обращение к базовой системе
                            ; ввода-вывода }
 procedure AuxOutchar(Port: word; Ch: char);
 inline(
   $58/          { POP AX   ; извлечь из стека символ }
   $5A/          { POP DX   ; извлечь из стека номер порта }
   $B4/$01       { MOV AH,1 ; инструкция для вывода }
   $CD/$14);     { INT 14H  ; обращение к базовой системе

                                - 49 -


                            ; ввода-вывода }
 function AuxInReady(Port:  word) : boolean;
 inline(
   $5A/          { POP DX   ; извлечь из стека номер порта }
   $B4/$03/      { MOV AH,3 ; состояние }

   $CD/$14/      { INT 14H  ; обращение к базовой системе
                            ; ввода-вывода }
   $88/$E0/      { MOV AL,AH ; получить в AН состояние
                            ; строки }
   $24/$01);     { AND AL,1 ; выделить бит готовности
                            ; данных }

 {$F+}

 function AuxInput(var F : TextRec) : integer;
 var
   P : integer;
 begin
   with F do
   begin
     P := 0;
     while AuxInReady(AuxPort) and (P<BufSize) do
     begin
       BufPtr^[P]:=AuxInChar(AuxPort); Inc(P);
     end;
     BufPos := 0; BufEnd := P;
    end;

 function AuxOutput(var F: TextRec): integer;
 var
   P: integer;
 begin
   with F do
   begin
   for P := 0 to BufPos-1 do AuxOutChar(AuxPort,BufPtr^[P]);
    BufPos := 0;
   end;

                                - 50 -


   AuxOutput := 0;
 end;

 function AuxIgnore(var F: TextRec) : integer;
 begin
    AuxIgnore := 0;
 end;

 function AuxOpen(var F: TextRec) : integer;
 begin
   with F do
   begin
     AuxInit(AuxPort,AuxPaaram);
     if Mode = fmInput then
     begin
       InOutFunc := @AuxInput;
       FlushFunc := @AuxIgnore;
     end else
   begin
     Mode      := fmOutput;
     InOutFunc := @AuxOutput;
     FlushFunc := @AuxOutput;
   end;
   CloseFunc := @AuxIgnore;
  end;
  AuxOpen := 0;
 end;

 {$F-}

 procedure AssignAux;
 begin
   with TextRec(F) do
   begin
     Handle   := $FFFF;
     Mode     := fmClosed;
     BufSize  := SizeOf(Buffer);
     BufPtr   := @Buffer;

                                - 51 -


     Openfunc := @AuxOpen;
     AuxPort  := Port;
     AuxParam := Param;
     Name[0]  := #0;
   end;
  end;

 end.

     Тип переменной ТехtRес, определяемой в модуле AuxInOut,
не совпадает точно с типом исходной переменной ТехtRес.
Вместо 16-байтового поля UserData она содержит два поля дли-
ной в слово, в которых запоминаются номер  коммуникационного
порта и байт параметра. За этими двумя полями следует 12 не-
используемых байтов (чтобы общее количество байтов равнялось
16). Процедура AuxInit инициализирует указанный коммуникаци-
онный порт в соответствии с заданным байтом параметра. Функ-
ция AuxInChar считывает символ из заданного порта. Процедура
AuxOutChar   выводит   символ   в   заданный  порт.  Функция
AuxInReady возвращает значение  Тruе в том случае, если сим-
вол готов для считывания из заданного порта. Обратите внима-
ние на использование  для реализации этих процедур и функций
внутренних директив. Дальнейшие подробности по использованию
коммуникационных  портов  описаны  в "Техническом справочном
руководстве по компьютеру IBM РС".
     Для обращения в заданному коммуникационному порту с оп-
ределенным байтом  параметра процедура AssignAux инициализи-
рует заданную переменную текстового файла. Номера портов 0 и
1 соответствуют портам СОМ1 и СОМ2.  Байт параметра описыва-
ется в "Техническом  справочном  руководстве  по  компьютеру
IBM PC".
     AuxLocn  инициализирует выбранный коммуникационный порт
и устанавливает  указатели  функции  в  соответствии с полем
Моdе. Отметим, что при выводе  FlushFunc  устанавливается на
тот же адрес, что  и  InOutFunc, что вызывает очистку буфера
текстового файла после каждой операции Write или Writeln.
     AuxInput  вводит символы (объем ввода задается перемен-
ной BufSize) из выбранного порта, а AuxOutput выводит содер-

                                - 52 -


жимое буфера в заданный порт.
     Функция  AuxIgnore используется в тех случаях, когда не
требуется  специальных  действий, как, например, для функций
Сlosе и Flush (в режиме ввода).
     Приведенная далее короткая  программа использует модуль
AuxInOut для записи строки  в  один из коммуникационных пор-
тов.  С помощью  процедуры AssingAUx файл Сом1 связывается с
портом  СОМ1.  При  этом используется скорость передачи 1200
бод, 1 стоп-бит, 8 битов данных и отсутствие проверки пари-
тета.

  program TestAux;
  uses AuxInOut;
  var
    Com1 : Text;
  begin
    AssignAux(Co1,0,$83);
    Rewrite(Com1);
    Writeln(Com1,'evice drivers are fun!');
    Close(Com1);
  end;

                      Процедуры выхода

     В помощью процедур выхода вы можете управлять процессом
завершения работы программы. Это полезно в том случае, когда
вы хотите перед прекращением работы программы обеспечить вы-
полнение  определенных  действий (типичным примером является
обновление и закрытие файлов).
     Реализовать  процедуру  выхода  вам  позволяет перемен-
ная-указатель ЕхitProc. Процедура выхода всегда получает вы-
зов при завершении работы программы, независимо от того, яв-
ляется ли это завершение нормальным окончанием  работы прог-
раммы, завершением после обращения к функции Наlt, или рабо-
та программы прекратилась из-за ошибки во время выполнения.
     Параметры для процедуры выхода не требуются и для того,
чтобы использовался дальний тип вызова, она должна компили-
роваться с указанием директивы компилятора {$F+}.

                                - 53 -


     Когда процедура выхода должным образом реализована, она
в действительности становится частью цепочки  процедур выхо-
да. Эта цепочка позволяет  реализовать  процедуры выхода как
для модулей, так и для  программ. В некоторых модулях проце-
дура выхода реализуется, как часть самого модуля, а выполне-
ние  некоторых  завершающих действий после выхода из модуля,
например, закрытие файлов или восстановление векторов преры-
ваний, возлагается на конкретную процедуру.  Процедуры в це-
почке выхода выполняются в последовательности,  обратной по-
рядку их реализации. Этим обеспечивается, что операторы  вы-
хода одного блока не выполняются,  пока не  будут  выполнены
операторы выхода какого-либо зависящего от него модуля.
     Чтобы сохранить цепочку выхода в неприкосновенности, вы
должны  перед  изменением указателя  ЕхitPrос на адрес вашей
собственной  процедуры  сохранить  текущее  содержимое этого
указателя. Далее, непосредственно перед возвратом управления
ваша процедура выхода должна должна восстановить сохраненное
значение ЕхitProc. В следующей программе показаны основы ме-
тода реализации такой процедуры выхода.

  program Testexit;
  var
    ExitSave: pointer;

  {$F+} procedure MyExit ; {$F-}
  begin
    .
    .
    .
    ExitProc := ExitSave;
    ExitProc := MyExit;
    .
    .
    .
  end.

     При входе в программу содержимое ЕхitProc сохраняется с
ЕхitSave,  а  затем  следует процедура выхода МуЕхit.  После

                                - 54 -


того, как она будет вызвана в качестве элемента процесса за-
вершения работы программы, процедура МуЕхit восстановит пре-
дыдущую процедуру выхода.
     Программа завершения в библиотеке  исполняющей  системы
будет вызывать процедуры выхода, пока  указатель ЕхitPrос не
примет значение nil.  Во избежании зацикливания ЕхitPrос ус-
танавливается в nil перед каждым обращением, так что следую-
щая  процедура  выхода  вызывается только в том случае, если
текущая процедура  выхода  устанавливает для ЕхitPrос ее ад-
рес. Если при выполнении  процедуры выхода возникает ошибка,
то в ней не успеет еще выполниться присваивание нового адре-
са указателю ЕхitPrос,  так как это делается непосредственно
перед тем, как процедура выхода выполнит возврат управления.
     Процедура выхода  может распознавать причину завершения
работы  программы  путем  проверки  целочисленной переменной
ЕхitCode и переменной-указателя  ErrorAddr.  В  случае  нор-
мального завершения в ЕхitCode содержится нулевое значение и
ErrorAddr имеет значение nil. В случает завершения через об-
ращение к процедуре Наlt ЕхitCode содержит  значение,  пере-
данное функции Наlt, а  ErrorAddr  имеет значение nil. Нако-
нец, в  случае  прекращения работы программы из-за ощибки во
время  ее  выполнения  ЕхitCode  содержит   код   ошибки,  а
ErrorAddr содержит адрес ошибочного оператора.
     Последняя процедура выхода (которая содержится в библи-
отеке исполняющей системы)  закрывает файлы Input и Output и
восстанавливает векторы прерываний, которые были перехвачены
Турбо-Паскалем.  При  этом,  если  указатель ErrorAddr имеет
значение, отличное от nil, то процедура выхода выводит сооб-
щение об ошибке во время выполнения программы. Если вы хоти-
те выводить свои  собственные  сообщения об ошибках во время
выполнения,  используйте процедуру выхода, которая проверяет
ErrorAddr и  выводит  сообщение об ошибке, если его значение
отлично от nil. В добавок в этому перед возвратом управления
необходимо  обеспечить,  чтобы указатель ErrorAddr был уста-
новлен в значение nil, чтобы  сообщение об ошибке не выдава-
лось снова другой процедурой выхода.
     После того, как  библиотека исполняющей системы обраща-
ется в  процедурам   выхода, она возвращает управление ДОС и

                                - 55 -


передает в  качестве  кода возврата значение, содержащееся в
ЕхitCode.

                Автоматическая оптимизация

     В  Турбо-Паскале  выполняется несколько различных типов
оптимизации кода, начиная от свертывания констант и вычисле-
ния булевских выражений по короткой схеме и кончая эффектив-
ным редактированием связей.  Рассмотрим некоторые виды опти-
мизации.

                    Свертывание констант

     Если участвующие в операции операнды представляют собой
константы перечислимого типа, то в Турбо-Паскале такое выра-
жение вычисляется во время компиляции.  Например,  выражение
Х:= 3+4*2 приведет к генерации такого же кода, как выражение
Х := 11. Аналогично, если операнды функций

  Abs, Sqr, Succ, Pred, Odd, Lo, Hi, Swap

представляют собой константы  перечислимого типа, то функция
вычисляется во время компиляции.
     Если индексом массива является констанда или выражение,
состоящее из констант, то адрес элемента вычисляется во вре-
мя компиляции.  Например, доступ к элементу Dаtа[5,5] так же
эффективен, как доступ к простой переменной.

                      Слияние констант

     Использование  одной  и той  же строковой константы два
или более раз приводит к генерации  только одной копии конс-
танты. Например, два или более оператора Write('Dоnе') в од-
ной и той же части программы  приведет к ссылке на одну и ту
же копию строковой константы 'Donе'.

                Вычисление по короткой схеме

     В Турбо-Паскале реализуется вычисление булевского выра-

                                - 56 -


жения по короткой  схеме.  Это  означает, что вычисление бу-
левского выражения  прекращается, как только результат всего
булевского выражения  становится очевидным. При этом обеспе-
чивается минимальное время выполнения и, обычно, минимальный
размер объектного кода.  Вычисление по короткой схеме делает
также  возможным  вычисление конструкций, которые иначе были
бы недопустимыми. Например:

 while (I<=Length(S)) and (S[I]<>' ') do Inc(I);
 while (P<>nil) and (P^.Value<>5) do P:=P^.Next;

     В обоих случаях, если первая  проверка  имеет  значение
Falsе, вторая проверка не вычисляется.
     Противоположным  вычислению по  короткой схеме является
полное вычисление, которое можно выбрать с помощью директивы
компилятора {$В+}. В этом случае  обеспечивается  вычисление
каждого операнда булевского выражения.

                     Порядок вычисления

     Стандартами Паскаля допускается, что операнды в выраже-
нии часто вычисляются в порядке, отличном от того, в котором
они записаны (слева направо). Например, оператор:

  I:=F(J) div G(J)

где F и G - функции целого типа,  приводит к тому, что G вы-
числяется перед вычислением F, так  как это позволяет компи-
лятору получить более оптимальный объектный код. Важно, поэ-
тому, чтобы выражение никогда не зависело от какого-то конк-
ретного порядка вычисления  встроенных  функций.  Если  вер-
нуться к  предыдущему  примеру, то  для  того, чтобы вызвать
функцию F перед функцией G, можно использовать временную пе-
ременную:

  T:=F(J); I:=T div G(J);


                                - 57 -


      Примечание:  Исключением из этого правила является вы-
      числение по  короткой  схеме  (разрешенное  директивой
      компилятора {$В-},  при  котором  операнды  булевского
      типа, связанные операциями and или оr, всегда вычисля-
      ются слева направо.

              Проверка на допустимость границ

     Присвоение  константы  переменной и использование конс-
танты  в  качестве  значения  параметра проверяется во время
компиляции на  допустимость  нахождения в заданных границах.
При этом генерируется такой код, что во время выполнения та-
ких проверок не делается.  Например, Х := 999, где Х - пере-
менная байтового типа (Bytе), приводит к ошибке компиляции.

           Использование двига вместо умножения

     Операция  Х*С,  где С -  константа, являющаяся степенью
числа 2, приводит к генерации объектного кода, в котором ис-
пользуется инструкция Shl (сдвиг влево).
     Аналогично,  когда размерность массива представляет со-
бой степень числа 2, то для  вычисления  индексных выражений
используется инструкция Shl (а не инструкция Мul).

        Автоматическое выравнивание на границу слова

     По умолчанию Турбо-Паскаль выравнивает все переменные и
типизованные  константы,  превышающие  по размеру 1 байт, на
границу машинного слова. На  всех  16-разрядных  процессорах
семейства 80х86 выравнивание на границу слова означает более
быстрое выполнение, поскольку доступ к элементам размером  в
слово  или четным адресам осуществляется быстрее, чем к сло-
вам по нечетному адресу.
     Выравнивание данных управляется директивой  компилятора
$A. По умолчанию в состоянии {$A+} переменные и типизованные
константы выравниваются указанным выше образом. В  состоянии
{$A-}  никаких  действий  по  выравниванию  не производится.
Дальнейшие подробности приведены в Приложении В  ("Директивы

                                - 58 -


компилятора").

               Удаление неиспользуемого кода

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

 if false then statement
 while false do statement

                    Эффективная компоновка

     Редактор связей  автоматически  удаляет  неиспользуемый
код (по процедурам), то есть процедуры и функции, являющиеся
частью скомпилированной программы, но к которым нет  обраще-
ний, не включаются в файл типа .ЕХЕ. Процедуры, фукнции, пе-
ременные и типизованные константы,  участвующие  в  процессе
компиляции,  но  ссылки на которые отсутствуют, удаляются из
файлй .EXE. Удаление  неиспользуемого  кода  выполянется  по
процедурам,  а  удаление неиспользуемых данных - по секциям,
где эти данные описываются.
     Рассмотрим следующую программу:

  program SmartLink;
  const
    H: array[0..15] of char = '0123456789ABCDEF';
  var
    I,J : integer;
    X,Y : real;
  var
    S: string[79];
  var
    A: array[1..10000] of integer;

  procedure P1:
  begin
   A[1] = 1;

                                - 59 -


  end;

  procedure P2;
  begin
    I := 1;
  end;

  procedure P3;
  begin
    S := 'Turbo Pascal';
    P2;
  end;

  begin
    P3;
  end;

     Основная программа вызывает процедуру Р3, которая вызы-
вает  процедуру Р2, поэтому обе процедуры Р2 и Р3 включаются
в файл .ЕХЕ. Поскольку Р2 ссылается на первый раздел  описа-
ния переменных, а Р3 ссылается на второй раздел описание пе-
ременных, переменные I, J, X, Y, S пзкже включаются в выпол-
няемый  файл.  Однако  на процедуру Р1 никаких ссылок нет, а
включенные в выполянемый файл процедуры не ссылаются на  пе-
ременные Н и A, поэтому эти объекты удаляются.
     Эффективная компоновка имеет особую ценность в связи  с
использованием  модулей, которые реализуют библиотеки проце-
дур и функций. Примером такого модуля  является  стандартный
модуль  Dos,  который  содержит  ряд процедур и функций. При
этом программа редко использует все эти процедуры. Если  она
использует  только  одну  или  две процедуры или функции, то
только эти процедуры включаются в  полученный  в  результате
файл .ЕХЕ, что позволяет значительно уменьшить его размер.

           Примечание: При компиляции в  память  эффективная
      компоновка  Турбо-Паскаля не работает. Этим объясняет-
      ся, почему некоторые программы становятся  меньше  при
      компиляции их на диск.
