Назад Домой! Дальше Глава 12. ИСПОЛЬЗОВАНИЕ ЯЗЫКА АССЕМБЛЕРА В ПРОГРАММАХ
НА TURBO PASCAL 7.0


Данный раздел не является справочным по языку ассемблера и предполагает знание читателем 
основ этого языка и устройство процессора 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      СХ,АХ	;cравнение с 0
             JLE      М3	        ;0 или отрицательное число
M1:          LODSW	        ;загрузка элемента массива
             СМР      АХ,ВХ	;сравнение с текущим максимумом
             JLE      M2	        ;не больше
             MOV      BX,AX	;новое максимальное число
М2:          LOOP     Ml	;цикл
М3:          MOV      АХ,ВХ	;результат функции
             POP      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 (старшая часть или адрес сегмента) и АХ (младшая часть или смещение), параметры 
размером в б байтов (типа 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);	{Исходный массив}
{$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). 
Здесь существует следующее правило: если подпрограмма объявляется в интерфейсной части 
какого-либо модуля, то она должна иметь дальнюю адресацию, в других случаях (подпрограмма 
объявляется в файле, содержащем основную программу, или в исполнительной части модуля)
следует использовать ближнюю адресацию. 
И еще одно замечание: внешнюю подпрограмму нельзя объявлять внутри другой подпрограммы.

12.2. Использование встроенного ассемблера
Начиная с версии 6.0 Turbo Pascal содержит встроенный ассемблер, позволяющий писать 
отдельные части программ на языке ассемблера. Встроенный ассемблер обладает многими 
возможностями языка Turbo assembler, но приспособлен к использованию в программах, 
написанных на языке Паскаль (позволяет использовать идентификаторы программы, написанные на 
языке Паскаль, комментарии, имеющие такой же вид, как в языке Паскаль, позволяет 
воспользоваться встроенным отладчиком для пошагового выполнения программы, контроля 
содержимого регистров и параметров программы и т. д.).

Так же как и Turbo assembler, встроенный ассемблер предполагает использование ряда 
предопределенных стандартных идентификаторов, имеющих специальное назначение. Если в 
программе будет введен идентификатор с таким же именем, но имеющий другое назначение, в 
частях программы, написанных на встроенном ассемблере, будет отдано предпочтение 
стандартному назначению этого идентификатора. Перечень стандартных идентификаторов 
встроенного ассемблера приведен в приложении В. 

Наряду с возможностью использования идентификаторов языка Паскаль встроенный ассемблер 
использует три дополнительных идентификатора:
@Code - текущий кодовый сегмент (используется только с оператором SEG); 
@Data - текущий сегмент данных  (используется только с оператором SEG);
@Result - результат, полученный функцией (можно использовать только внутри функции).

При использовании встроенного ассемблера нельзя использовать: 
 - стандартные процедуры и функции;
 - специальные массивы Mem, MemW, MemL, Port и PortW (см. п. 13); 
 - константы типа string, вещественных типов и типа-множества; 
 - процедуры и функции, объявленные с директивой inline; 
 - метки, объявленные не в данном блоке. 

Часть программы, написанная на языке ассемблера, помещается в операторные скобки asm...end.
В следующем примере приведена программа, выполняющая те же функции, что и предыдущие 
программы, но использующая встроенный ассемблер. 

Пример. Программа, использующая подпрограмму-функцию, определяющую максимальный элемент из 
массива целых чисел и написанную на языке ассемблера, но использующую встроенный ассемблер. 

program  EXAMPLE22;
const
  N = 7;	{Размер массива}
  Massiv: array [1..n]	of Integer =
  (1, 2, 3, 2, 17, 7, 2);	{Исходный массив}
function Max(var Mas;	N: Integer): Integer;
begin
     asm
       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      @Result,BX	{результат функции}
     end 
end;

begin
  WriteLn('Максимальное число массива равно: ',
  Max(Massiv,N));
  ReadLn 
end.

Следует заметить, что при использовании встроенного ассемблера комментарии пишутся таким же 
образом, как и в языке Паскаль (в фигурных скобках). Назначение же точки с запятой несколько 
другое - этим знаком отделяются друг от друга команды, написанные на одной строке (такая 
возможность при использовании встроенного ассемблера существует), 
например:

LDS SI,Mas; XOR	AX,AX;	MOV BX,8001h

Как видно из примера, можно использовать идентификаторы, объявленные на языке Паскаль 
(параметры Mas, N). Нет необходимости сохранять и восстанавливать регистр ВР, удалять из 
стека передаваемые параметры, включая и локальные, и т. д. 

12.3. Использование директивы ASSEMBLER
Если ту или иную подпрограмму нужно полностью написать на языке ассемблера, используя 
встроенный ассемблер, можно вместо операторных скобок asm...end использовать директиву 
assembler, которая имеет ряд особенностей.
 - Во-первых, все передаваемые параметры, размером отличные от 1, 2 или 4 байт, передаются 
   всегда своим адресом без создания копии в стеке.
 - Во-вторых, нельзя использовать для передачи результата функции переменную @Result. 
   Результат передается точно так же, как и при использовании TASM (см. п. 12.1). 
   Исключение составляет результат типа string. В этом случае в переменной @Result находится 
   адрес строки, в которую следует поместить полученную информацию. 
 - В-третьих, для процедур и функций, не имеющих формальных и локальных параметров, вообще 
   не выделяется область стека.
 - В-четвертых, так же как и в предыдущем случае, автоматически оформляется начало и конец 
   подпрограммы, связанные с сохранением регистра ВР и освобождением стека от передаваемых 
   параметров.
Ниже приведен пример использования такой директивы.

Пример. Программа, использующая подпрограмму-функцию, определяющую максимальный элемент из 
массива целых чисел и написанную на языке ассемблера, но использующую встроенный ассемблер 
и директиву assembler. 

program EXAMPLE23;
const
  N = 7;  {Размер массива}
  Massiv: array [1..n]	of Integer =
  (1, 2, 3, 2, 17, 7, 2);	{Исходный массив}
function Max(var Mas;	N: Integer):	Integer; assembler;
asm
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 {результат функции}
end;

begin
  WriteLn('Максимальное число массива равно: ',
  Max(Massiv,N));
  ReadLn 
end.

12.4. Использование оператора или директивы INLINE 
Для коротких подпрограмм (до десятка операторов) можно использовать непосредственное задание 
всей подпрограммы или ее части в кодах процессора, используя оператор или директиву inline. 
Отличие оператора от директивы заключается в том, что оператор может быть использован в 
подпрограмме совместно с другими операторами, написанными на языках Паскаль или ассемблера, 
а директива предполагает, что подпрограмма целиком состоит лишь из нее одной.
Само написание оператора или использование директивы inline практически ничем не отличается 
друг от друга. Каждый из них начинается с зарезервированного слова inline, за которым в 
круглых скобках через прямой слеш (/) записываются байты или слова кодов команд. При этом 
каждый элемент будет либо байтом, либо словом в зависимости от фактического значения (байт, 
если значение в пределах 0..255, слово - в остальных случаях). Этот стандартный размер можно 
изменить, используя указатель размера < или >. Если используется указатель размера <, 
информация размещается в одном байте, причем, если ее размер больше одного байта, 
используется только младший байт. Указатель размера > всегда размещает информацию в одном 
16-разрядном слове (см. пример ниже). В качестве элементов оператора или директивы inline 
можно использовать идентификаторы языка Паскаль (в директиве нельзя только использовать 
идентификаторы передаваемых в подпрограмму параметров).
 
Пример. Программа, использующая подпрограмму-функцию, определяющую максимальный элемент из 
массива целых чисел, написанную на языке ассемблера и использующую оператор inline.

program  EXAMPLE24;
const
  N = 7;  {Размер массива}
  Massiv: array [1..n] of Integer =
  (1, 2, 3, 2, 17, 7, 2);   {Исходный массив}
function Max(var Mas;	N: Integer): Integer;
begin
inline(
$C5/$76/6/	{LDS  SI,Mas 5}
$B8/>$0/	{MOV  AX,0}
$BB/$8001/	{MOV  BX,8001h}
$8B/$4E/4/	{MOV  CX,N}
$3B/$C8/	{CMP  CX.AX}
$7E/$09/	{JLE  @@3}
$AD/            {@@1: LODSW}
$3B/$C3/	{CMP  AX.BX}
$7E/$02/	{JLE  002}
$8B/$D8/	{MOV  BX,AX}
$Е2/$F7);       {@@2: LOOP@@1}
asm
MOV AX,BX       {@@3: MOV AX.BX}
end 
end;

begin
WriteLn('Максимальное число массива равно: ',
Max(Massiv, N)); 
ReadLn 
end. {работает неверно, где-то опечатка}

Несколько иначе используется директива inline. При использовании этой директивы подпрограмма 
представляет собой макроопределение ассемблера, и при обращении к ней ее коды помещаются в 
место обращения. В этом случае не формируются команды обращения к подпрограмме и выхода из 
нее. Если подпрограмме передаются параметры, они, как обычно, передаются через стек, из 
которого их можно взять. Такой вариант используется для очень коротких подпрограмм и дает 
наиболее эффективный результат. В качестве примера рассмотрим все ту же задачу поиска в 
массиве максимального числа, хотя используемая здесь подпрограмма и несколько великовата для 
данного варианта. 

Пример.  Программа, использующая подпрограмму-функцию, определяющую максимальный элемент из 
массива целых чисел, написанную на языке ассемблера и использующую директиву inline. 
program EXAMPLE25; 
const
  N = 7;	{Размер массива}
  Massiv: array[1..n] of Integer =
  (1, 2, 3, 2, 17, 7, 2);   {Исходный массив}
function Max(var Mas; N: Integer): Integer;
inline(
$59/	        {POP   CX       число элементов}
$5E/     	{POP   SI       смещение массива}
$1F/	        {POP   DS	адрес сегмента}
$33/$C0/	{XOR	AX,AX}
$BB/$8001/	{MOV	BX,8001h}
$3B/$C8/	{CMP	CX,AX}
$7E/$09/	{JLE	@@3}
$AD/            {@@1:	LODSW}
$3B/$C3/	{CMP	AX,BX}
$7E/$02/	{JLE	@@2}
$8B/$D8/	{MOV	BX,AX}
$E2/$F7/        {@@2:	LOOP @@l} 
$8B/$C3); 
begin
  WriteLn('Максимальное число	массива равно: ',
  Max(Massiv, N)); 
  ReadLn 
end.
Rambler's Top100
Hosted by uCoz