12. Использование языка ассемблера в программах на Turbo Pascal 7
Данный раздел не является справочным по языку ассемблера и предполагает знание читателем основ этого языка и устройство процессора 80X86.
Turbo Pascal позволяет писать отдельные части программы (подпрограммы или части подпрограмм) на языке ассемблера. Здесь возможны четыре варианта.
Во-первых, можно написать подпрограмму на языке ассемблера, скомпилировать ее отдельно компилятором TASM (Turbo Assembler) с получением объектного файла, а затем скомпоновать его с основной программой, написанной на Turbo Pascal, используя при этом директиву компилятора {$L <имя файла>}, где <имя файла> - имя файла с подпрограммой на ассемблере, и директиву external.
Во-вторых, используя встроенный ассемблер пакета Turbo Pascal, отдельные части текста программы можно написать непосредственно на языке ассемблера, заключив их в операторные скобки asm...end.
В-третьих, ту или иную подпрограмму (процедуру или функцию) можно полностью, за исключением заголовка, написать на языке ассемблера, используя при этом директиву assembler. В этом случае также используется встроенный ассемблер.
Наконец, в-четвертых, можно небольшую подпрограмму написать непосредственно в кодах процессора, используя оператор или директиву inline.
При написании отдельных частей программы на языке ассемблера следует иметь в виду, что необходимо сохранить содержимое регистров ВР, SP, SS и DS. Если их необходимо изменить, то исходные значения следует запомнить, а затем восстановить. Остальные регистры можно безболезненно изменять.
Основным вопросом стыковки программы с подпрограммой, написанной на ассемблере, является передача параметров в подпрограмму и обратно. Именно этому вопросу и будет здесь уделено основное внимание.
Ниже будут рассмотрены особенности использования этих вариантов. В качестве примера их использования будут приведены различные варианты подпрограммы-функции, определяющей максимальный элемент из массива целых чисел.
12.1. Использование компилятора TASM
Как правило, этот вариант применяется, когда та или иная программа имеет большой размер и ее целесообразно и написать, и скомпилировать отдельно, используя компилятор TASM [5]. В этом случае можно использовать все возможности языка и компилятора TASM.
Пример. Программа, использующая подпрограмму-функцию, определяющую максимальный элемент из массива целых чисел и написанную на языке ассемблера.
Основная программа, использующая подпрограмму, написанную на языке ассемблера, содержит инициализированный массив, в котором будет определяться максимальное число, а сама программа выводит на экран значение максимального числа из этого массива:
program EXAMPLE20; |
|
const |
|
N = 7 |
{Размер массива} |
Massiv: array[1..n] of Integer = (1, 2, 3, 2, 17, 7, 2); |
{Исходный массив} |
{$L SUBR} |
{Подключение файла SUBR.OBJ} |
function Max(var Mas; N: Integer): Integer; external; |
|
begin |
|
WriteLn('Максимальное число массива равно: ' , Max(Massiv, N)); |
|
ReadLn |
|
end. |
|
Используя стандартную модель памяти, подпрограмму, определяющую максимальное число из массива, можно написать следующим образом:
CODE SEGMENT BYTE PUBLIC |
|
ASSUME |
CS:CODE |
|
|
|
PUBLIC |
Max |
|
;внешний идентификатор |
AdrMas |
EQU |
DWORD |
PTR[BP+6] |
;адрес первого параметра |
N |
EQU |
WORD |
PTR[BP+4] |
;второй параметр |
Max |
|
PROC |
NEAR |
|
|
PUSH |
BP |
|
;сохранение регистра ВР |
|
MOV |
BP,SP |
|
;указатель стека |
|
LDS |
SI,AdrMas |
|
;адрес массива |
|
XOR |
AX, AX |
|
;0 - в регистр АХ |
|
MOV |
BX,8001h |
|
;минимальное целое число |
|
MOV |
CX,N |
|
;число элементов массива |
|
CMP |
CX,AX |
|
;сравнение с 0 |
|
JLE |
M3 |
|
;0 или отрицательное число |
M1: |
LODSW |
|
|
;загрузка элемента массива |
|
CMP |
AX, BX |
|
;сравнение с текущим максимумом |
|
JLE |
M2 |
|
;не больше |
|
MOV |
BX,AX |
|
;новое максимальное число |
M2: |
LOOP |
M1 |
|
;цикл |
M3: |
MOV |
AX,BX |
|
;результат функции |
|
POP |
BP |
|
;восстановление регистра |
BP |
|
|
|
|
|
RET |
6 |
|
;возврат из подпрограммы |
Max |
|
ENDP |
|
|
CODE |
ENDS |
|
|
|
|
END |
|
|
|
По приведенной подпрограмме следует сделать следующие замечания. Первые две команды - сохранение регистра ВР и загрузка в него указателя стека - являются типичными командами, с помощью которых можно установить доступ к передаваемым параметрам через регистр ВР.
Параметры передаются в подпрограмму следующим образом. Параметры-значения размером в один байт передаются одним 16-разрядным словом, причем информативным является младший байт, параметры-значения в 2 байта передаются одним 16-разрядным словом, в 4 байта - двумя 16-разрядными словами, параметры-значения типа Real передаются тремя 16-разрядными словами, все остальные параметры-значения (в том числе и 3-байтовые) передаются своими полными адресами. Из этого правила есть некоторые исключения: параметры-переменные и параметры-константы всегда передаются своими полными адресами.
Т. к. в подпрограмме первый параметр является параметром-переменной, то он передается своим адресом, с помощью которого в дальнейшем и извлекаются элементы массива. Второй параметр подпрограммы - параметр-значение, и он передается своим значением. Первый параметр находится по адресу ВР+6, а второй - ВР+4. Указанные смещения определяются наличием в стеке адреса возврата (при ближней адресации - 2 байта), размещенным в стеке значением регистра ВР (2 байта) и для первого параметра - размером второго параметра (2 байта).
Если подпрограмма является подпрограммой-функцией, то возвращаемый параметр передается различным образом в зависимости от своего размера. Параметр размером в байт передается в регистре AL, параметр размером в 2 байта - в регистре АХ, параметр размером в 4 байта - в регистрах DX (старшая часть или адрес сегмента) и АХ (младшая часть или смещение), параметры размером в 6 байтов (типа Real) - в регистрах DX (старшая часть), ВХ (средняя часть) и АХ (младшая часть). Параметры других вещественных типов передаются в нулевом элементе стека сопроцессора ST(0). Если функция возвращает значение типа string, то при обращении к функции резервируется память для размещения возвращаемой строки, а адрес этой области размещается в стеке выше всех передаваемых параметров.
В рассматриваемом примере возвращаемый параметр - типа Integer, и он возвращается в регистре АХ.
При возвращении из подпрограммы в команде RET записан аргумент 6 для удаления из стека передаваемых параметров, которые в данном примере имеют именно этот размер.
Turbo Assembler предполагает и другое оформление подпрограмм, используемых затем в программах, написанных на языке Паскаль. Для этого используется специальная модель памяти Large (большая), задаваемая в виде:
.MODEL Large,PASCAL.
Она позволяет несколько упростить оформление входа в подпрограмму и выхода из нее. Подпрограмма дополняется необходимыми командами на этапе компиляции.
Пример. Вариант предыдущей подпрограммы, использующий специальную модель памяти.
|
.MODEL |
Large,PASCAL |
; специальная модель памяти |
|
.CODE |
|
|
|
|
PUBLIC |
Max |
|
;внешний идентификатор |
Max |
|
PROC |
NEAR Mas: DWORD, N: WORD |
|
|
|
|
;передаваемые параметры |
|
LDS |
SI,Mas |
|
;адрес массива |
|
XOR |
AX, AX |
|
;0 - в регистр АХ |
|
MOV |
BX,8001h |
|
;минимальное целое число |
|
MOV |
CX,N |
|
;число элементов массива |
|
CMP |
CX,AX |
|
;сравнение с 0 |
|
JLE |
@@3 |
|
;0 или отрицательное число |
@@1: |
LODSW |
|
|
;загрузка элемента массива |
|
CMP |
AX,BX |
|
;сравнение с текущим максимумом |
|
JLE |
@@2 |
|
;не больше |
|
MOV |
BX,AX |
|
;новое максимальное число |
@@2: |
LOOP |
@@1 |
|
; цикл |
@@3: |
MOV |
AX,BX |
|
;результат функции |
|
RET |
|
|
;возврат из подпрограммы |
Max |
|
ENDP |
|
|
|
END |
|
|
|
В этом примере не сохраняется и не восстанавливается регистр ВР - эти операции добавляются к программе на этапе компиляции. Не указывается также и размер передаваемых параметров - они при выходе из подпрограммы удаляются автоматически. В строке, где начинается описание подпрограммы (начинается с имени подпрограммы - Мах), необходимо перечислить все передаваемые параметры в том же порядке, как они заданы в заголовке, написанном на языке Паскаль с указанием их размеров (о размерах передаваемых параметров см. выше).
Здесь показана также возможность использования в подпрограммах локальных меток, начинающихся символами @@.
В подпрограмме, написанной на языке ассемблера, можно использовать подпрограммы, написанные на языке Паскаль. Несколько модифицированная подпрограмма определения максимального элемента массива, которая в случае недопустимого числа элементов массива (0 или отрицательное число) вызывает подпрограмму, написанную на языке Паскаль для выдачи сообщения, приведена в следующем примере.
Пример. Модифицированный вариант программы, в котором подпрограмма, написанная на языке ассемблера, в случае недопустимого числа элементов массива (равно 0 или отрицательное) вызывает подпрограмму, написанную на языке Паскаль, выводящую соответствующее сообщение.
Основная программа, содержащая подпрограмму на языке Паскаль, будет иметь следующий вид:
program EXAMPLE21;
const
N = 7 {Размер массива}
Massiv: array[1..n] of Integer = (1, 2, 3, 2, 17, 7, 2); {Исходный масcив}
{$l SUBR} {Подключение файла SUBR, OBJ}
Function Max(var Mas; N :Integer) Integer; external;
procedure ErrorReport(N: Integer);
begin
WriteLn;
WriteLn('Недопустимое число элементов: ' , N);
ReadLn
end;
begin
WriteLn('Максимальное число массива равно: ', Max(Massiv, N));
ReadLn
end.
Подпрограмма, написанная на языке ассемблера, будет в этом случае иметь следующий вид:
|
.MODEL |
Large,PASCAL |
; специальная модель памяти |
|
.CODE |
|
|
|
|
EXTRN |
ErrorReport: |
NEAR |
;внешняя подпрограмма |
|
PUBLIC |
Max |
|
;внешний идентификатор |
Max |
|
PROC |
NEAR Mas: DWORD, N: WORD |
|
|
|
|
;передаваемые параметры |
|
LDS |
SI,Mas |
|
;адрес массива |
|
XOR |
AX, AX |
|
;0 - в регистр АХ |
|
MOV |
BX,8001h |
|
;минимальное целое число |
|
MOV |
CX,N |
|
;число элементов массива |
|
CMP |
CX,AX |
|
;сравнение с 0 |
|
JG |
@@1 |
|
; допустимое число |
|
PUSH |
BX |
|
;сохранение регистра ВХ |
|
PUSH |
CX |
|
; передаваемый параметр |
|
CALL |
ErrorReport |
|
;обращение к подпрограмме |
|
POP |
BX |
|
;восстановление регистра ВХ |
|
JMP |
@@3 |
|
;на завершение |
@@1: |
LODSW |
|
|
;загрузка элемента массива |
|
CMP |
AX,BX |
|
;сравнение с текущим максимумом |
|
JLE |
@@2 |
|
;не больше |
|
MOV |
BX,AX |
|
;новое максимальное число |
@@2: |
LOOP |
@@1 |
|
; цикл |
@@3: |
MOV |
AX,BX |
|
;результат функции |
|
RET |
|
|
;возврат из подпрограммы |
Max |
|
ENDP |
|
|
|
END |
|
|
|
Перед обращением к подпрограмме, написанной на языке Паскаль, в стек в соответствующем порядке следует поместить передаваемые параметры. В данном случае такой параметр один - число элементов массива.
Т. к. подпрограмма, написанная на языке Паскаль, не гарантирует сохранение регистров АХ, ВХ, СХ и DX, то в случае необходимости сохранения их значений следует перед обращением к подпрограмме, написанной на языке Паскаль, сохранить в стеке значения соответствующих регистров, а после возвращения из подпрограммы - восстановить их. В данном примере сохраняется содержимое регистра ВХ, в котором записано минимальное целое число.
При написании программ, содержащих отдельные части, написанные на языках ассемблера и Паскаль, следует обращать внимание на способ адресации (дальний - far или ближний - near). Здесь существует следующее правило: если подпрограмма объявляется в интерфейсной части какого-либо модуля, то она должна иметь дальнюю адресацию, в других случаях (подпрограмма объявляется в файле, содержащем основную программу, или в исполнительной части модуля) следует использовать ближнюю адресацию.
И еще одно замечание: внешнюю подпрограмму нельзя объявлять внутри другой подпрограммы.
|