Как правило, этот вариант применяется, когда та или иная программа имеет большой размер и ее целесообразно и написать, и скомпилировать отдельно, используя компилятор TASM. В этом случае можно использовать все возможности языка и компилятора TASM.
Основная программа, использующая подпрограмму, написанную на языке ассемблера, содержит инициализированный массив, в котором будет определяться максимальное число, а сама программа выводит на экран значение максимального числа из этого массива:
function Max(var Mas; N:integer); integer; external;
{$L test.obj);
{$F-}
begin
Wtiteln(‘Максимальное число массива равно: ’, Max(Massiv, N));
end.
Используя стандартную модель памяти, подпрограмму, определяющую максимальное число из массива, можно написать следующим образом:
P386N
Ideal
Model Small, PASCAL
DATASeg
CODESeg
PROC Max Far
Public Max
arg @@AdrMass:dword, @@n:word
uses ds, si, ax, bx, cx
lds si, [@@AdrMass]
mov bx, 8001h
mov cx, [@@N]
cmp cx, 0
jle @@3
@@1: lodsw
cmp ax, bx
jle @@2
mov bx, ax
@@2: loop @@1
@@3: mov ax, bx
ret
EndP
End
Параметры передаются в подпрограмму следующим образом. Параметры-значения размеров в 1 байт передаются одним 16-разрядным словом, причем информативным является младший байт, параметры-значения в 2 байта передаются одним 16-разрядным словом, в 4 байта — двумя 16-разрядными словами, параметры типа Real передаются тремя 16-разрядными словами, все остальные параметры-значения (в том числе и трехбайтовые) передаются своими полными адресами. Параметры-переменные и параметры-константы всегда передаются своими полными адресами.
Так как в подпрограмме первый параметр является параметром-переменной, то он передается своим адресом, с помощью которого в дальнейшем и извлекаются элементы массива. Второй параметр подпрограммы — параметр-значение, и он передается своим значением. Первый параметр находится по адресу bp+8, а второй — bp+6. Указанные смещения определяются наличием в стеке адреса возврата, размещенным в стеке значением регистра bp.
Если подпрограмма является подпрограммой-функцией, то возвращаемый параметр передается различным образом в зависимости от своего размера. Параметр размером в 1 байт передается в регистре AL, параметр размером в 2 байта передается в регистре AX, параметр размером в 4 байта — в регистрах DX (старшая часть или адрес сегмента) и AX (младшая часть или смещение), параметры размером в 6 байт (типа Real) — в регистрах DX (старшая часть), BX (средняя часть) и AX (младшая часть). Если функция возвращает значение типа string, то при обращении к функции резервируется память для размещения возвращаемой строки, а адрес этой области размещается в стеке выше всех передаваемых параметров.
В рассматриваемом примере возвращаемый параметр типа integer, и он возвращается в регистре AX.
В подпрограмме, написанной на языке ассемблера, можно использовать подпрограммы, написанные на языке Паскаль. Несколько модифицированная подпрограмма определения максимального элемента массива, которая в случае недопустимого элемента массива (0 или отрицательное число) вызывает подпрограмму, написанную на языке Паскаль для выдачи сообщения.
function Max(var Mas; N:integer); integer; external;
{$L test.obj);
{$F-}
procedure ErrorReport(N: integer);
begin
Writeln(‘Недопустимое число элементов’, N);
end;
begin
Wtiteln(‘Максимальное число массива равно: ’, Max(Massiv, N));
end.
Подпрограмма, написанная на языке ассемблера, будет в этом случае иметь следующий вид:
P386N
Ideal
Model Small, PASCAL
DATASeg
CODESeg
Extrn ErrorReport: near
PROC Max Far
Public Max
arg @@AdrMass:dword, @@n:word
uses ds, si, ax, bx, cx
lds si, [@@AdrMass]
mov bx, 8001h
mov cx, [@@N]
cmp cx, 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
EndP
End
Перед обращением к подпрограмме, написанной на языке Паскаль, в стек в соответствующем порядке следует поместить передаваемые параметры. В данном случае такой параметр один — число элементов массива.
Так как подпрограмма, написанная на языке Паскаль, не гарантирует сохранения регистров AX, BX, CX и DX, то в случае необходимости сохранения их значений следует перед обращением к подпрограмме, написанной на языке Паскаль, сохранить в стеке значения соответствующих регистров, а после возвращения из подпрограммы — восстановить их. В данном примере сохраняется содержимое регистра BX, в котором записано минимальное число.
При написании программ, содержащих отдельные части, написанные на языке ассемблера и Паскаль, следует обращать внимание на способ адресации (дальний — far или ближний — near). Здесь существует следующее правило: если подпрограмма объявляется в интерфейсной части какого-либо модуля, то она должна иметь дальнюю адресацию, в других случаях (подпрограмма объявляется в файле, содержащим основную программу, или в исполнительной части модуля) следует использовать ближнюю адресацию. Внешнюю подпрограмму нельзя объявлять внутри другой подпрограммы.
Использование встроенного ассемблера
Встроенный ассемблер обладает многими возможностями языка Turbo assembler, он приспособлен к использованию в подпрограммах, написанных на языке Паскаль (позволяет использовать идентификаторы программы, написанные на языке Паскаль, комментарии, имеющие такой же вид, как в языке Паскаль, позволяет воспользоваться встроенным отладчиком для пошагового выполнения программы, контроля содержимого регистров и параметров программы и т.д.).
Так же как и Turbo assembler, встроенный ассемблер предполагает использование ряда предопределенных стандартных идентификаторов, имеющих специальное назначение. Если в программе будет введен идентификатор с таким же именем, но имеющий другое назначение, в частях программы, написанных на встроенном ассемблере, будет отдано предпочтение стандартному назначению этого идентификатора.
Наряду с возможностью использования идентификаторов языка Паскаль, встроенный ассемблер использует три дополнительных идентификатора:
@Code — текущий кодовый сегмент (используется только с оператором SEG);
@Data — текущий сегмент данных (используется только с оператором SEG);
@Result — результат, полученный функцией (можно использовать только внутри функции).
При использовании встроенного ассемблера нельзя использовать:
— стандартные процедуры и функции;
— специальные массивы Mem, MemW, MemL, Port и PortW;
— константы типа string, вещественных типов и типа-множества;
— процедуры и функции, объявленные с директивой inline;
— метки, объявленные не в этом блоке.
Часть программы, написанная на языке ассемблера, помещается в операторные скобки asm . . . end.
Wtiteln(‘Максимальное число массива равно: ’, Max(Massiv, N));
end.
При использовании встроенного ассемблера комментарии пишутся таким же образом, как и в языке Паскаль. Назначение точки с запятой другое — этим знаком отделяются друг от друга команды, написанные на одной строке, например:
lds si, Mas; xor ax,ax; mov bx, 8001h
Использование директивы ASSEMBLER
Если ту или иную подпрограмму нужно полностью написать на языке ассемблера, используя встроенный ассемблер, можно вместо операторных скобок asm … end использовать директиву assembler, которая имеет ряд особенностей.
Во-первых, все передаваемые параметры, размером отличные от 1, 2 или 4 байт, передаются всегда своим адресом без создания копии в стеке.
Во-вторых, нельзя использовать для передачи результата функции переменную @Result. Результат передается точно так же, как и при использовании TASM. Исключение составляет результат типа string. В этом случае в переменной @Result находится адрес строки, в которую следует поместить полученную информацию.
В-третьих, для процедур и функций, не имеющих формальных и локальных параметров, вообще не выделяется область стека.
В-четвертых, так же как и в предыдущем случае, автоматически оформляется начало и конец подпрограммы, связанные с сохранением регистра ВР и освобождением стека от передаваемых параметров.
function Max(var Mas; n:integer): integer; assembler;
asm
lds si, Mas
xor ax,ax
mov bx, 8001h
mov cx, ax
jle @@3
@@1: lodsw
cmp ax, bx
jle @@2
mov bx,ax
@@2: loop @@1
@@3: mov ax, bx
end;
begin
Wtiteln(‘Максимальное число массива равно: ’, Max(Massiv, N));
end.
Массивы Mem, MemW и MemL
В Turbo Pascal реализованы три предопределенных массива Mem, MemW и MemL. Эти массивы используются для прямого доступа к памяти. Каждый элемент массива Mem представляет собой байт, каждый элемент массива MemW — это слово, а каждый элемент массива MemL является значением двойного слова.
Для индексов массивов Mem используется специальный синтаксис: для того чтобы задать базовый сегмент и смещение для ячейки памяти, к которой производится доступ, используется два выражения целого типа длиной в слово, разделанные двоеточием.
Mem[$0040:$$0049] := 7;
Data := MemW[Seg(v):Ofs(v)];
MemLond := MemL[64:3*4];
Первый оператор сохраняет значение 7 в байте $0040:$0049. Второй оператор помещает значение длиной в слово, записанное в первых двух байтах переменной V, в переменную Data. Третий оператор помещает значение длинного целого типа, записанное по адресу $0040:$000C, в переменную MemLong.