|
|||||||||||||||||||
|
ГЛАВА 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 программирование: Бурлаков Михаил