Мир программирования

 


Найти: на:


Меню
Партнеры
Счетчики
Реклама

ГЛАВА 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 не реентерабельная.

 

[начало] [оглавление]    

 


Предыдущая страница     |     Следующая страница


Добавить в избанное Обсудить в форуме Написать автору сайта Версия для печати

Опрос

Конкурсы
Реклама

 

Web дизайн: Бурлаков Михаил    

Web программирование: Бурлаков Михаил

Используются технологии uCoz