| 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). Здесь существует следующее правило: если подпрограмма объявляется в интерфейсной части какого-либо модуля, то она должна иметь дальнюю адресацию, в других случаях (подпрограмма объявляется в файле, содержащем основную программу, или в исполнительной части модуля) следует использовать ближнюю адресацию. И еще одно замечание: внешнюю подпрограмму нельзя объявлять внутри другой подпрограммы. 
 
 |