Обеспечение стека
Турбо Паскаль ожидает, что перед возвратом управления из подпрограммы все параметры в стеке центрального процессора будут удалены.
Есть два способа настройки стека. Вы можете использовать инструкцию RET N (где N - это число байт передаваемых, то есть занесенных в стек, параметров), либо сохранить адрес возврата в регистрах (или в памяти) и извлечь параметры из стека поочередно. Такую технику извлечения полезно использовать для оптимизации по скорости при работе с процессором 8086 или 8088 (самые "медленные" процессоры серии), когда на адресацию типа "база плюс смещение" затрачивается минимум 8 циклов за обращение. Это позволяет также сэкономить место, так как инструкция POP занимает только один байт.
Примечание: Если вы используете директивы .MODEL, PROC и ARG, то Ассемблер автоматически добавляет во все инструкции RET число байт извлекаемых параметров.
Доступ к параметрам
Когда получает управление ваша подпрограмма на Турбо Ассемблере, вершина стека будет содержать адрес возврата (два или четыре слова, в зависимости от того, является ли подпрограмма ближней или дальней), а далее будут находится передаваемые параметры.
Примечание: При вычислении адресов параметров нужно принимать во внимание регистры, такие как BP, содержимое которых также может быть занесено в стек.)
Существует три основных метода доступа к параметрам, передаваемых Турбо Паскалем вашей подпрограмме на Турбо Ассемблере. Вы можете:
использовать для адресации к стеку регистр BP;
для получения параметров использовать другой базовый или индексный регистр;
извлечь из стека адрес возврата, а затем параметры.
Первый и второй методы более сложны, и мы расскажем о них в следующих двух разделах. Третий метод предусматривает извлечение из стека и сохранение адреса возврата, а затем извлечения параметров и записи их в регистры. Лучше всего этот метод работает, когда ваша подпрограмма не требует пространства для локальных переменных.
Использование для адресации к стеку регистра BP
Первый и наиболее часто используемый метод доступа к параметрам, передаваемым из Турбо Паскаля в Турбо Ассемблер, заключается в том, чтобы использовать для адресации к стеку регистр BP. Например:
CODE
MyProc
j
i
| SEGMENT ASSUME CS:CODE PROC FAR PUBLIC MyProc EQU WORD PTR [bp+6]
EQU WORD PTR [bp+8] push bp
mov bp,sp
mov ax,i . . .
|
; procedure MyProc(i,j : integer);
; j находится над сохраненным BP ; и адресом возврата ; i располагается над j ; нужно сохранить BP вызывающей ; программы ; BP теперь указывает на вершину ; стека ; адресуемся к i через BP
|
При вычислении смешений в стеке параметров, к которым мы обращаемся таким образом, нужно помнить, что 2 байта используются для сохраненного регистра BP.
Обратите внимание на использование в данном примере присваиваний. Они позволяют сделать программу более понятной. У них есть только один недостаток: поскольку для выполнения такого рода присваиваний можно использовать только директиву EQU (а не =), в данной исходном файле Турбо Ассемблера вы не сможете переопределить идентификаторы i и j. Один из способов обойти это заключается в том, чтобы использовать более описательные имена параметров, чтобы они не повторялись, либо можно ассемблировать каждую подпрограмму Ассемблера отдельно.
Директива ARG
Хотя можно обращаться к параметрам через регистр BP, Турбо Ассемблер предусматривает альтернативу вычислению смещений в стеке и выполнению текстовых присваиваний. Это директива ARG. При использовании ее в процедуре директива ARG автоматически определяет смещения параметров относительно регистра BP. Она вычисляет также размер блока параметров и использует его в инструкции RET. Поскольку идентификаторы, создаваемые по директиве ARG, определены только в соответствующей процедуре, в каждой процедуре или функции вам не требуется использовать уникальные имена параметров.
Покажем, как будет выглядеть пример предыдущего раздела, если переписать его, используя директиву ARG:
CODE SEGMENT ASSUME CS:CODE MyProc PROC FAR ; procedure MyProc(i,j : integer); external; PUBLIC MyProc ARG j : WORD, i : WORD = RetBytes push bp ; нужно сохранить BP вызывающей программы mov bp,sp ; BP теперь указывает на вершину стека mov ax,i ; адресуемся к i через BP . . .
Директива ARG Турбо Ассемблера создает локальные идентификаторы для параметров i и j. На время выполнения процедуры строка:
ARG j : WORD, i : WORD = RetBytes
автоматически приравнивает идентификатор i к [WORD PTR BP+6], идентификатор j к [WORD PTR BP+8], а идентификатор RetBytes - к числу 4 (размеру в байтах блока параметров). В значениях учитывается и занесенное в стек значение BP, и размер адреса возврата: если бы процедура MyProc имела ближний тип, то i было бы приравнено к значению [BP+4], j - к [BP+6], а RetBytes также было бы равно 4 (в любом случае процедура MyProc может завершить выполнение с помощью инструкции RET RetBytes).
При использовании директивы ARG нужно помнить, что параметры должны перечисляться в обратном порядке. Последний параметр процедуры или функции Турбо Паскаля нужно размещать в директиве ARG первым и наоборот.
Относительно использования директивы ARG с Турбо Паскалем можно сделать еще одно замечание. В отличие от других языков, Турбо Паскаль всегда заносит в стек параметр-значение размером в байт, как 16-битовое слово. При этом сообщить Турбо Ассемблеру о дополнительном байте должны вы. Предположим, например, что вы написали функцию, описание которой в Паскале выглядит следующим образом:
function MyProc(i, j : char) : string; external;
Директива ARG для этой функции должна была бы выглядеть так:
ARG j: BYTE: 2, i:BYTE: 2 = RetBytes RETURN result: DWORD
Здесь 2 после каждого аргумента необходимо указывать для того, чтобы сообщить Ассемблеру, что каждый идентификатор заносится в стек, как массив из 2 байт (где, в данном случае, младший байт каждой пары содержит полезную информацию).
В функции, возвращающей строковое значение (как данная функция), параметр RETURNS в директиве ARG позволяет вам определить переменную, приравненную к тому месту в стеке, которое указывает на временный результат функции. Переменная в RETURNS на размер (в байтах) блока параметров.
Турбо Паскаль и директива .MODEL
Директива .MODEL с параметром TPASCAL задает упрощенную сегментацию, модель памяти и языковую поддержку. Обычно используется большая модель памяти (large) Ранее мы уже видели, что нужно сделать в программах Ассемблера, чтобы можно было использовать процедуры и функции Паскаля. Преобразуем пример, используя в нем директивы .MODEL и PROC:
.MODEL large, PASCAL .CODE MyProc PROC FAR i:BYTE,j:BYTE result:DWORD PUBLIC MyProc mov ax,i . . . ret
Заметим, что теперь не нужно задавать параметры в обратном порядке. Не требуется также масса других операторов. Использование в директиве .MODEL ключевого слова PASCAL задает использование соглашений Паскаля, определяет имена сегментов, выполняет инструкции PUSH BP и MOV BP,SP и задает также возврат с помощью инструкций POP BP и RETn (где n - число байт параметров).
Использование другого базового или индексного регистра
Второй способ доступа к параметрам состоит в использовании для получения этих параметров другого базового или индексного регистра (BX, SI или DI). Нужно однако помнить, что по умолчанию сегментным регистром для них является регистр DS, а не SS. Поэтому для их использования вам придется применять префикс переопределения сегмента.
Приведем пример использования для получения параметров регистра BX:
CODE SEGMENT ASSUME CS:CODE MyProc PROC FAR ; procedure MyProc(i,j : integer); PUBLIC MyProc j EQU WORD PTR SS:[BX+4] ; j находится над сохраненным BP и адресом возврата i EQU WORD PTR SS:[bp+8] ; i располагается над j mov bx,sp ; BX теперь указывает на вершину стека mov ax,i ; адресуемся к i через BX . . .
В тех программах, где нет большого числа ссылок на параметры, такой метод позволяет сэкономить время и место. Почему? Потому, что в отличие от BP, регистр BX не требуется восстанавливать в конце программы.
1 2 3 4 5 6 7 8
8 8 8
| |