                             ГЛАВА 18.

                         ВОПРОСЫ КОНТРОЛЯ.

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


                       Соглашения о вызовах.

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

     Push Param1
     Push Param2
     .
     .
     .
     Push ParamX
     Call ProcOrFunc

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


                       Изменяемые параметры.

     Изменяемый параметр (var параметр) всегда передается по ссылке
- указатель  указывает  на   действительное   положение   параметра
(адрес).


                      Неизменяемые параметры.

     Неизменяемый параметр передается по ссылке или по  значению  в
зависимости  от  типа и размера параметра.  Если значение параметра
занимает 1,  2 или 4 байта,  то значение помещается в стек. Иначе в
стек помещается  указатель  на  значение,  и  процедура или функция
затем копирует это значение в локальную область памяти.

     Примечание: 8086 не поддерживает однобайтовые  инструкции Push
и Pop, поэтому однобайтовые параметры всегда помещаются в стек, как
слова.  Младший байт слова содержит значение,  а  старший  байт  не
используется (не определен).

     Целый тип  или  параметр,  передаваемый  как  байт,  слово или
двойное слово всегда использует такой  же  формат,  как  переменная
целого типа. (Для двойного слова старшее слово помещается в стек до
младшего слова так, что младшее слово имеет младший адрес).
     Параметр символьного типа передается как беззнаковый байт.
     Параметр логического  типа  передается как байт со значением 0
или 1.
     Параметр перечислимого типа передается как  беззнаковый  байт,
если   перечисление   имеет  256  или  меньше  значений,  иначе  он
передается как беззнаковое слово.
     Параметр типа  Real  передается  как  6 байт в стек и является
исключением из правил,  что только 1,  2 или 4-x байтовые  значения
передаются прямо через стек.
     Параметр вещественного типа (Real,  Single, Double, Extended и
Comp)  передается  как  4,  6,  8  или  10  байт  в стек и является
исключением из правил,  что только 1,  2 или  4  байтовые  значения
передаются прямо в стек.

     Примечание: Turbo  Pascal  версии 4.0 передавал параметры типа
8087  (Ringle,  Double,  Extended  и  Comp)  во   внутренний   стек
математического  сопроцессора  8087.  Для  совместимости  с другими
языками и  предотвращения  переполнения  стека  8087,  эта   версия
использует  стек  8086.

     Параметр типа Рointer передается как двойное слово (сегментная
часть передается до части смещения так, что часть смещения занимает
младшие адреса).
     Параметр типа String передается, как указатель на значение.
     Параметр типа   множество   передается,   как   указатель   на
"неупакованное" множество, которое занимает 32 байта.
     Массивы и  записи  длиной 1,  2 или 4 байта передаются прямо в
стек. Другие массивы и записи передаются как указатель на значение.


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

     Результаты функции порядкового типа (Integer,  Char, Boolean и
перечислимый) передаются в регистрах процессора: байты передаются в
AL,  слова  передаются  в  AX  и  двойные  слова передаются в DX:AX
(старшее слово в DX, младшее слово в AX).
     Результат функции типа Real возвращается в регистрах DX:BX:AX.
(Старшее слово в DX, среднее слово в BX, младшее слово в AX).
     Результаты функций типа 8087 (Single, Double, Extended и Comp)
передаются в регистр вершины стека 8087 (ST(0)).
     Результат функции  типа Pointer передается в DX:AX (сегментная
часть в DX, часть смещения в AX).
     Для результатов  функций  типа  String,  вызывающая  программа
помещает указатель на временную область памяти до  помещения  любых
параметров, и функция возвращает строковое значение в эту временную
память. Функция не должна удалять указатель.


              Ближние и дальние вызовы (NEAR и FAR).

     Процессор 8086  поддерживает  два  типа  инструкций  вызова  и
возврата: NEAR и FAR. NEAR (ближняя) инструкция передает управление
в другую  точку  внутри того же кодового сегмента,  а FAR (дальняя)
инструкция позволяет изменить кодовый сегмент.
     Инструкция NEAR  CALL  помещает  16-ти  битовый адрес возврата
(только смещение) в стек,  а  инструкция  FAR  CALL  помещает  32-х
битовый адрес  возврата  (и  сегмент  и смещение).  Соответствующие
инструкции RET выталкивают из стека только смещение или  и смещение
и сегмент.
     Turbo Pascal   на   основе    объявления    процедуры    будет
автоматически   выбирать   правильную   модель  вызова.  Процедуры,
объявленные в секции interfaсe  модуля,  будут  FAR,  так  как  они
вызываются  из других модулей.  Процедуры,  объявленные в программе
или в секции implementation модуля,  будут NEAR,  так как они могут
быть вызваны только внутри программы или модуля.
     Для некоторых специальных целей  может  требоваться  процедура
типа  FAR.  Например,  в  программах  с  оверлеями  все процедуры и
функции должны быть FAR;  кроме того,  если процедура  или  функция
присваивается  процедурной  переменной,  она также должна быть FAR.
Директива компилятора $F используется  для  отмены  автоматического
выбора    модели   вызова   компилятором.   Процедуры   и   функции
откомпилированные в состоянии {$F+} всегда FAR; в состоянии {$F-} -
Turbo Pascal автоматически выбирает правильную модель. Состояние по
умолчанию {$F-}.


                  Вложенные процедуры и функции.

     Процедура или   функция   называется   вложенной,   когда  она
объявлена  внутри  другой  процедуры  или  функции.  По   умолчанию
вложенные процедуры и функции всегда используют модель вызова NEAR,
поскольку они "видимы" только  внутри  определенной  процедуры  или
функции, находящейся в том же кодовом сегменте. Однако в оверлейных
программах  директива  {$F+}  используется  для  установления  всех
процедур и функций в FAR, включая и вложенные.
     Когда вызывается вложенная процедура или  функция,  компилятор
генерирует инструкцию PUSH BP перед CALL, в результате передавая BP
вызывающей  процедуре,  как  дополнительный   параметр.   Поскольку
вызванная   процедура  устанавливает  свой  собственный  BP,  к  BP
вызывающей процедуры можно обратиться как к  слову,  хранящемуся  в
[BP  + 4] или [BP + 6],  если процедура FAR.  Используя связь через
[BP + 4] или [BP + 6],  вызываемая  процедура  может  обращаться  к
локальным переменным из стека вызывающей. Если вызывающая процедура
в свою очередь является вложенной,  она также имеет связь через [BP
+ 4] или [BP + 6]. Следующий пример демонстрирует, как обращаться к
локальным переменным из оператора inline во вложенной процедуре:

     procedure A; near;
     var IntA: Integer;

     procedure B; far;
     var IntB: Integer;

     procedure C; near;
     var IntC: Integer;

     begin
        inline(
        $8B/$46/<IntC/     {MOV AX,[BP+IntC]      ; AX=IntC}
        $8B/$5E/$04/       {MOV BX,[BP+4]         ; BX=стек B}
        $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=стек A}
        $36/$8B/$47/<IntA/ {MOV AX,SS:[BX+IntA]   ; AX=IntA}
        );
     end;
     begin end;
     begin end;

     Примечание: Вложенные  процедуры  и  функции  не  могут   быть
объявлены с  директивой  Еxternal  и они не могут быть процедурными
параметрами.


                        Код входа и выхода.

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

     push   bp               ; сохранить bp
     mov    bp,sp            ; установить стек
     sub    sp,LocalSize     ; распределить локальные переменные

     где LocalSize - размер локальных  переменных.  Инструкция  sub
вставляется, если LocalSize не равно 0. Если модель процедуры NEAR,
параметры начинаются с bp+4; если FAR - то с bp+6.
     Стандартный код выхода:

     mov   sp,bp         ;удалить локальные переменные
     pop   bp            ;восстановить bp
     ret   ParamSize     ;удалить параметры и вернуться

     где ParamSize  -  размер параметров.  Инструкция ret -это NEAR
или FAR возврат, в зависимости от модели вызова процедуры.


                      Соглашения о регистрах.

     Процедуры и функции должны сохранять регистры BP, SP, SS и DS.
Все другие регистры можно изменять.


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

     Установив процедуру  выхода,  Вы  можете получить контроль над
процессом  завершения  программы.  Это  полезно,  когда  Вы  хотите
сделать  надежным  выполнение  действий  до  завершения  программы;
типичный пример - обновление и закрытие файлов.
     Переменная типа  уазатель  ExitProc  позволяет  Вам установить
процедуру выхода.  Процедура выхода  всегда  вызывается  как  часть
завершения   программы,   в   том   числе  нормального  завершения,
завершения через вызов Halt или  завершения  из-за  ошибки  времени
выполнения.
     Процедура выхода   не   имеет   параметров   и   должна   быть
откомпилирована  с директивой {$F+} для установления дальней модели
вызова.
     При правильной   установке,   процедура  выхода  действительно
становится частью  цепочки  процедур  выхода.  Эта  цепочка  делает
возможным инсталляцию  процедур  выхода для модулей также как и для
программы.  Некоторые модули инсталируют процедуру выхода как часть
их кода инициализации,  это гарантирует, что данная процедура будет
вызвана для очистки  после  модуля;  например,  закрыть  файлы  или
восстановить   вектора   прерываний.   Процедуры   цепочки  выхода,
выполняются в   порядке,   обратном   порядку   инсталляции.    Это
гарантирует,   что   выходной   код  любого  из  модулей  не  будет
выполняться до выполнения  выходного  кода  модулей,  зависящих  от
него.
     Чтобы сохранить  цепочку  выхода  неповрежденной,  Вы   должны
сохранить  текущее  содержимое  ExitProc  до изменения ее адреса на
Вашу  процедуру  выхода.  Кроме  того,  первый  оператор  в   Вашей
процедуре выхода должен восстановить запомненное значение ExitProc.
Следующая  программа  демонстрирует  метод   реализации   процедуры
выхода:

     program Textexit;
     var
        ExitSave: Pointer;

     procedure MyExit; far;
     begin
        ExitProc := ExitSave; {сначала сохраняет старый вектор}
       ...
     end;

     begin
        ExitSave := ExitProc;
        ExitProc := @MyExit;
        ...
     end.


     При входе программа сохраняет содержимое ExitProc в ExitSave и
затем инсталлирует процедуру выхода MyExit.  MyExit, после того как
она была вызвана как  часть  процесса  завершения,  восстанавливает
предыдущую процедуру выхода.
     Программа завершения библиотеки времени  выполнения продолжает
вызывать  процедуры  выхода  до  тех  пор,  пока ExitProc не станет
равной  nil.  Чтобы  предотвратить   бесконечный   цикл,   ExitProc
устанавливается   в  nil  до  каждого  вызова,  так  что  следующая
процедура выхода вызывается только тогда,  когда текущая  процедура
выхода   присвоит   адрес  в  ExitProc.  Если  в  процедуре  выхода
произойдет ошибка, то она не вызывается снова.
     Процедура выхода   может   анализировать   причину  завершения
проверкой  целочисленной  переменной  ExitCode  и  переменной  типа
pointer ErrorAddr.
     В случае нормального завершения ExitCode равна 0 и ErrorAddr -
nil.  В  случае  завершения  после  вызова Halt,  ExitCode содержит
значение переданное в Halt и ErrorAddr -  nil.  Наконец,  в  случае
завершения  из-за ошибки времени выполнения,  ExitCode содержит код
ошибки и ErrorAddr - адрес ошибочного оператора.
     Последняя процедура    выхода   (инсталированная   библиотекой
времени выполнения) закрывает файлы Input и Output, восстанавливает
вектора прерываний,  установленные Turbo Pascal.  Кроме того,  если
ErrorAddr  не  nil,  то  выдается  сообщение  об   ошибке   времени
выполнения.
     Если Bы хотите  обработать  ошибку  времени  выполнения  сами,
инсталлируйте процедуру   выхода,  которая  проверяет  ErrorAddr  и
выдает сообщение если он не nil.  Наконец,  до возврата  установите
ErrorAddr в nil,  так чтобы другие процедуры выхода не выдали снова
сообщение об ошибке.
     После того  как  библиотека  времени  выполнения  вызвала  все
процедуры  выхода,  она  передает  управление  в   DOS,   передавая
значение, хранящееся в ExitCode, как код возврата.


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

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


                   Написание процедур прерывания.

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

     procedure IntHandler(Flags, CS, IP, AX, BX, CX, DX,
                          SI, DI, DS, ES, BP: Word);
     interrupt;
     begin
     ...
     end;

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

     procedure IntHandler(DI,ES,BP : Word); {неверно}

     procedure IntHandler(SI,DI,DS,ES,BP : Word); {неверно}

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

     push   ax
     push   bx
     push   cx
     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

     Заметьте, отсутствие инструкции STI для разрешения прерываний.
Вы должны кодировать ее сами (если требуется), используя инструкцию
Inline.  Выходной  код   восстанавливает   регистры   и   выполняет
инструкцию возврата:

     mov    sp,bp
     pop    bp
     pop    es
     pop    ds
     pop    di
     pop    si
     pop    dx
     pop    cx
     pop    bx
     pop    ax
     iret

     Процедура прерывания   может  модифицировать  свои  параметры.
Изменяя   объявленные   параметры,   Вы    будете    модифицировать
соответствующие  регистры  при  возврате из обработчика прерываний.
Это может быть полезным, когда вы используете обработчик прерываний
как пользовательский сервис, например, как сервис DOS INT 21H.
     В процедурах  прерываний,  которые   обрабатывают   аппаратные
прерывания,  не  должны использоваться любые программы ввода/вывода
Turbo Pascal  и  программы  динамического   распределения   памяти,
поскольку  они  не  реентерабельные.  Кроме  того,  не  могут  быть
использованы функции DOS, так как DOS не реентерабельная.
