Ч А С Т Ь 4. ИСПОЛЬЗОВАНИЕ TURBO PASCAL С ЯЗЫКОМ АССЕМБЛЕРА. ГЛАВА 22. ВСТРОЕННЫЙ АССЕМБЛЕР. Встроенный Ассемблер Turbo Pascal позволяет Вам писать ассемблерный код для 8086/8087 и 80286/80287 прямо в Ваших программах на Паскале. Конечно, Вы еще можете преобразовывать ассемблерные инструкции в машинный код вручную для использования в операторах inline или подредактироватьь .OBJ файлы, которые содержат external процедуры и функции, когда Вы хотите смешивать Паскаль и Ассемблер. Встроенный Ассемблер реализует большое подмножество синтаксиса, поддерживаемого Turbo Assembler и макроассемблером Microsoft. Встроенный Ассемблер поддерживает все коды операций 8086 /8087 и 80286/80287 и почти все операторы выражений Turbo Assembler. За исключением DB, DW, DD (определить байт, слово и двойное слово) ни одна из директив Turbo Assembler, таких как EQU, PROC, STRUC, SEGMENT и MACRO не поддерживается встроенным Ассемблером. Однако операции, поддерживаемые директивами Turbo Assembler, во многом соответствуют соответствующим конструкциям Turbo Pascal. Например большинство директив EQU соответствует объявлениям const, var и type в Turbo Pascal, директива PROC соответствует объявлениям procedure и function, а директива STRUC соответсвует типам record в Turbo Pascal. В действительности можно думать о встроенном Ассемблере Turbo Pascal, как о компиляторе с языка Ассемблер, который использует синтаксис Паскаля для всех объявлений. Оператор asm. К встроенному Ассемблеру обращаются через оператор asm. Синтаксис оператора asm: asm AsmStatement < Separator AsmStatement > end где AsmStatement - это ассемблерный оператор, а Separator - это ";", новая строка или комментарий Паскаля. Несколько примеров оператора asm: if EnableInts then asm sti end else asm cli end; asm mov ax,Left; xchg ax,Right; mov Left,ax; end; asm mov ah,0 int 16H mov CharCode,al mov ScanCode,ah end; asm push ds lds si,Source les di,Dest mov cx,Count cld rep movsb pop ds end; Заметим, что на одной строке можно поместить несколько операторов Ассемблера, разделенных ";". Заметим так же, что ";" не требуется между двумя ассемблерными операторами, если они на разных строках. Наконец заметим, что ";" не указывает, что оставшаяся часть строки - комментарий, т.к. комментарий должен быть написан в стиле Паскаля, используя {} и (* *). Использование регистров. Правила использования регистров в операторе asm в общем такие же, как и в external процедурах и функциях. Оператор asm должен сохранять регистры BP, SP, SS и DS и может свободно изменять регистры AX, BX, CX, DX, SI, DI, ES и Flags. На входе оператора asm BP указывает на текущий стек, SP указывает на вершину стека, SS содержит сегментный адрес сегмента стека, а DS содержит сегментный адрес сегмента данных. За исключением BP, SP, SS и DS, оператор asm не должен делать предположений о содержимом остальных регистров при входе в оператор. Синтаксис ассемблерных операторов. Синтаксис ассемблерного оператора: [ Label ":" ] < Prefix > [ Opcode [ Operand < "," Operand > ] ] где Label - идентификатор метки, Prefix - код префикса, Opcode - директива или инструкция Ассемблера и Opеrand - ассемблерное выражение. Комментарии разрешены между ассемблерными операторами, но не внутри их. Например, это разрешено: asm mov ax,1 {Initial value} mov cx,100 {Count} end; а это нет: asm mov {Initial value} ax,1 mov cx,{Count} 100 end; Метки. Метки определяются в Ассемблере так же, как в Паскале, записывая идентификатор метки с двоеточием до оператора; как и в Паскале, метки, определенные в Ассемблере, должны объявляться в декларативной части label в блоке, содержащем оператор asm. Однако существует одно исключение из этого правила: локальные метки. Локальные метки - это метки, которые начинаются с @. Поскольку @ не может быть частью идентификатора Паскаля, использование таких локальных меток допускается только внутри оператора asm, который определяет их (т.е. сфера действия локальной метки расширяется от ключевого слова asm до ключевого слова end для этого оператора asm). Примечание: В отличие от обычных меток, локальные метки не объявляются в разделе объявления label до их использования. Идентификатор локальной метки состоит из символа @ с последующей одной или более буквой A..Z, цифр 0..9, "_" или @. Как для всех меток, после идентификатора идет ":". Следующий фрагмент программы демонстрирует использование локальных и глобальных меток в операторе asm: label Start, Stop; ... begin asm Start: ... jz Stop @1: ... loop @1 end; asm @1: ... jc @2 ... jmp @1 @2: end; goto Start; Stop: end; Заметим, что нормальная метка может быть определена внутри оператора asm и использована вне оператора asm, и наоборот. Заметим также, что одно и то же имя локальной метки может использоваться в различных операторах asm. Префикс. Встроенный ассемблер поддерживает встроенные префиксы: ─────────────────────────────────────────────────────────────────── LOCK Захват шины REP Повтор строковой операции REPE/REPZ Повтор строковой операции пока =/0 REPNE/REPNZ Повтор строковой операции пока (не =)/(не 0) SEGCS Перекрытие CS (сегмент кода) SEGDS Перекрытие DS (сегмент данных) SEGES Перекрытие ES (экстра сегмент) SEGSS Перекрытие SS (сегмент стека) ─────────────────────────────────────────────────────────────────── С ассемблерной инструкцией могут быть указаны 0 или более префиксов. Например: asm rep movsb SEGES lodsw SEGCS mov ax,[bx] SEGES mov WORD PTR [DI],0 end; Заметим, что префикс может быть указан без кода инструкции в том же операторе - в этом случае префикс воздействует на код инструкции в следующем ассемблерном операторе. Код инструкции очень редко имеет более одного префикса и не может быть указано более 3 префиксов (LOC, затем SEGxx, затем REPxx). Будьте внимательны при использовании нескольких префиксов - их порядок важен и некоторые процессоры 80х86 не могут обрабатывать все комбинации правильно. Например, 8086 или 8088 помнят только префикс REPxx, если в середине строковой инструкции возникает прерывание. Поэтому префиксы LOC и SEGxx не могут быть надежно закодированы до REPxx в строковой инструкции. Коды инструкций. Встроенный Ассемблер поддерживает все коды инструкций 8086/8087 и 80286/80287. Коды 8087 допустимы только в состоянии {$N +} (числовой процессор разрешен), коды 80286 допустимы только в состоянии {$G+} ( генерация кода 80286 разрешена), и коды 80287 разрешены только в состоянии {$G+,N+}. Полное описание этих инструкций см. в руководствах по 80х86 и 80х87. Размер инструкции RET. Инструкция RET генерирует ближний или дальний возврат в зависимости от модели вызова текущей процедуры или функции. procedure NearProc; near; begin asm ret {генерирует ближний вызов} end; end; procedure FarProc; far; begin asm ret {генерирует дальний вызов} end; end; С другой стороны, инструкции RETN и RETF всегда генерируют ближний возврат и дальний возврат, вне зависимости от модели вызова текущей процедуры или функции. Автоматический размер перехода. Если не было указано противное, встроенный Ассемблер оптимизирует инструкции перехода, автоматически выбирая самую короткую, и, следовательно, самую эффективную инструкцию перехода. Автоматический выбор инструкции перехода применяется к инструкции безусловного перехода JMP и ко всем инструкциям условного перехода, когда назначение - метка (а не процедура или функция). Для инструкции безусловного перехода JMP встроенный Ассемблер генерирует короткий переход (1 байт кода операции и 1 байт смещения), если расстояние до метки назначения внутри диапазона от -128 до 127 байт; иначе генерируется ближний переход (1 байт кода операции и 2 байта смещения). Для инструкции условного перехода генерируется короткий переход (1 байт кода и 1 байт смещения), если расстояние до метки назначения от -128 до 127 байт; иначе генерируется короткий переход с обратным условием, который обходит ближний переход на метку назначения (5 байт в итоге). Например оператор JC Stop где Stop не внутри диапазона короткого перехода, преобразуется в машинный код jnc Skip jmp Stop Skip: Переходы на точки входа процедур и функций всегда либо ближние, либо дальние, и никогда не короткие, а условные перехода на процедуры и функции не разрешены. Вы можете указать встроенному Ассемблеру генерировать безусловный ближний или дальний переход на метку, используя конструкцию NEAR PTR или FAR PTR. Например операторы jmp NEAR PTR Stop jmp FAR PTR Stop всегда генерируют ближний и дальний переходы соответственно, даже если Stop внутри диапазона короткого перехода. Директивы Ассемблера. Встроенный Ассемблер Turbo Pascal поддерживает 3 директивы Ассемблера: DB, DW, DD (определить байт, слово и двойное слово) Они генерируют данные, соответствующие операндам, разделенным запятыми, в этой директиве. Директива DB генерирует последовательность байт. Каждый операнд может быть константным выражением со значением от -128 до 255 или строкой символов любой длины. Константное выражение генерирует 1 байт кода, а строка генерирует последовательность байт со значениями, соответствующими ASCII кода каждого символа. Директива DW генерирует последовательность слов. Каждый операнд может быть константным выражением со значением от -32768 до 65535 или адресным выражением. Для адресного выражения встроенный Ассемблер генерирует ближний указатель, который содержит смещение этого адреса. Директива DD генерирует последовательность двойных слов. Каждый операнд может быть константным выражением со значением от -2,147,483,648 до 4,294,967,295 или адресным выражением. Для адресного выражения встроенный Ассемблер генерирует дальний указатель, который содержит и смещение и сегментную часть адреса. Данные, генерируемые директивами DB, DW, DD всегда хранятся в кодовом сегменте так же, как код, генерируемый другими операторами встроенного Ассемблера. Чтобы генерировать неинициализированные или инициализированные данные, в сегменте данных Вы должны использовать обычные объявления Var или Const Паскаля. Примеры директив DB, DW, DD: asm DB 0FFH DB 0,99 DB 'A' DB 'Hello world...',0DH,0AH DB 12,"Turbo Pascal" DW 0FFFFH DW 0,9999 DW 'A' DW 'BA' DW MyVar DW MyProc DD 0FFFFFFFFH DD 0,999999999 DD 'A' DD 'DCBA' DD MyVar DD MyProc end; Примечание: В Turbo Assembler, когда идентификатор стоит перед директивой DB, DW и DD, это приводит к объявлению переменной размером в байт, слово или двойное слово по адресу этой директивы. Например, Turbo Assembler разрешает: ByteVar DB ? WordVar DW ? ... mov al,ByteVar mov bx,WordVar Встроенный Ассемблер не поддерживает такое объявление переменных. В Turbo Pascal единственный символ, который может быть определен в операторе встроенного Ассемблера - это метка. Все переменные должны быть объявлены с использованием синтаксиса Паскаля и предыдущий пример соответствует: var ByteVar: Byte; WordVar: Word; ... asm mov al,ByteVar mov bx,WordVar end; Операнды. Операнды встроенного Ассемблера - это выражения, которые состоят из комбинации констант, регистров, символов и операторов. Хотя выражения встроенного Ассемблера строятся с использованием тех же основных принципов, что и выражения Паскаля, существует ряд важных отличий, которые объясняются ниже. Внутри операндов следующие зарезервированные слова имеют во встроенном Ассемблере предопределенный смысл: AH CL FAR SEG AL CS HIGH SHL AND CX LOW SHR AX DH MOD SI BH DI NEAR SP BL DL NOT SS BP DS OFFSET ST BX DWORD OR TBYTE BYTE DX PTR TYPE CH ES QWORD WORD XOR Зарезервированные слова всегда имеют приоритет над идентификаторами пользователя. Например фрагмент кода: VAR Ch: Char; ... asm mov ch,1 end; загружает 1 в регистр CH, а не в переменную Ch. Для доступа к символу, определенному пользователем с тем же именем, Вы должны использовать & для перекрытия оператора: asm mov &ch,1 end; Мы настоятельно рекомендуем Вам избегать определения идентификаторов с теми же именами, что и зарезервированные слова встроенного Ассемблера, поскольку это может привести к труднонаходимым ошибкам. Выражения. Встроенный Ассемблер вычисляет все выражения, как 32-битовые целые выражения; он не поддерживает значения с плавающей точкой и строковые значения, за исключением строковых констант. Выражения встроенного Ассемблера строятся из элементов выражения и операторов и каждое выражение связано с классом и типом выражения. Эти концепции объясняются в следующих разделах. Различия между выражениями Паскаля и Ассемблера. Наиболее важное отличие между выражениями Паскаля и встроенного Ассемблера в том, что выражения встроенного Ассемблера должны разрешаться в константное значение, т.е. значение должно быть вычислено во время компиляции. Например, для объявлений const X = 10; Y = 20; var Z: Integer; следующий оператор разрешен: asm mov Z,X+Y end; Поскольку X и Y - константы, выражение X+Y наиболее удобный способ написания константы 30, а результирующая инструкция будет пересылать непосредственное значение 30 в переменную Z, размером в слово. Но если Вы объявите X и Y как переменные: var X, Y: Integer; встроенный Ассемблер не сможет вычислить значение X+Y во время компиляции. Встроенный Ассемблер для пересылки суммы X+Y генерирует: asm mov ax,X add ax,Y mov Z,ax end; Другое важное отличие между выражениями Паскаля и встроенного Ассемблера в способе интерпретации переменных. В выражении Паскаля ссылка на переменную интерпретируется как содержимое переменной, а в выражении встроенного Ассемблера ссылка на переменную означает адрес этой переменной. Например, в Паскале выражение X+4, где Х - переменная, означает содержимое X+4, а во встроенном Ассемблере это означает содержимое слова с адресом на 4 байта выше, чем адрес Х. Так даже если Вы можете написать asm mov ax,X+4 end; этот код не загружает значение Х+4 в AX, а загружает значение слова, хранящегося по адресу на 4 байта выше Х. Корректный способ добавить 4 к содержимому Х: asm MOV AX,X ADD AX,4 end; Элементы выражения. Основные элементы выражения - это константы, регистры и символы. Константы. Встроенный Ассемблер поддерживает 2 типа констант: числовые константы и строковые константы. Числовые константы. Числовые константы должны быть целыми и их значения должны быть в диапазоне от -2,147,483,648 до 4,294,967,295. Числовые константы по умолчанию используют десятичную нотацию, но встроенный Ассемблер так же поддерживает двоичную, 8-ричную и 16 -ричную нотации. Двоичная нотация выбирается написанием В после числа, 8-ричная нотация выбирается написанием буквы О после числа и 16-ричная нотация выбирается написанием Н после числа или $ до числа. Примечание: Суффиксы B, O, H не поддерживаются в выражениях Паскаля. Выражения Паскаля допускают только десятичную нотацию (по умолчанию) и 16-ричную нотацию (используя префикс $). Числовые константы должны начинаться с одной из цифр 0..9 или символа $; так, когда Вы пишите 16-ричную константу, используя суффикс Н, требуется дополнительный 0 в начале числа, если первая значащая цифра одна из 16-ричных цифр А..F. Например, 0BAD4H и $BAD4 - 16-ричные константы, а BAD4H - идентификатор, поскольку начинается с буквы, а не с цифры. Строковые константы. Строковые константы должны быть заключены в кавычки или апострофы. Две последовательные "" или '' внутри строки считаются как один символ. Примеры строковых констант: 'Z' 'Turbo Pascal' "That's all folks" '"That"s all folks,"he said.' '100' '"' "'" Заметим, что в 4 строке использовались 2 апострофа для указания одиночного символа "апостроф". В директивах DB разрешены строковые константы любой длины. Это приводит к распределению последовательности байт, содержащей ASCII значения символов строки. Во всех других случаях строковая константа не может быть длиннее 4 символов и означает, числовое значение, которое может использоваться в выражении. Числовое значение строковой константы вычисляется как Ord(Ch1) + Ord(Ch2) shl 8 + Ord(Ch3) shl 16 + Ord(Ch4) shl 24 где Ch1 наиболее правый (последний) символ, а Ch4 - наиболее левый (первый) символ. Если строка короче 4 символов, самые левые символы устанавливаются в 0. Несколько примеров строковых констант и соответствующих числовых значений: 'a' 00000061H 'ba' 00006261H 'cba' 00636261H 'dcba' 64636261H 'a ' 00006120H ' a' 20202061H 'a'*2 000000E2H 'a'-'A' 00000020H not'a' FFFFFF9EH Регистры. Следующие зарезервированные символы означают регистры 8086: 16-битный общего назначения AX BX CX DX 8-битный младший AL BL CL DL 8-битный старший AH BH CH DH 16-битный указатель или индекс SP BP SI DI 16-битный сегментный регистр CS DS SS ES регистр стека 8087 ST Когда операнд состоит только из имени регистра, он называется регистровым операндом. Все регистры могут быть использованы как регистровые операнды. Кроме того некоторые регистры могут использоваться в других констекстах. Базовые регистры (BX, BP) и индексные регистры (SI, DI) могут быть написаны внутри [] для указания индексации. Допустимые комбинации базовых/индексных регистров: [BX], [BP], [SI], [DI], [BX +SI], [BX+DI], [BP+SI], [BP+DI]. Сегментные регистры (ES, CS, SS, DS) могут использоваться вместе с ":" как перекрытие сегмента для указания сегмента, отличного от того, который процессор выбирает по умолчанию. Символ S означает самый верхний регистр из регистров стека 8087. Каждый из 8 регистров с плавающей точкой может быть использован с помощью ST(x), где х - константа от 1 до 7, указывающая на смещение от вершины стека. Символы. Встроенный Ассемблер позволяет Вам обращаться почти ко всем символам Паскаля в ассемблерных выражениях, включая метки, константы, типы, переменные, процедуры и функции. Дополнительно встроенный Ассемблер реализует следующие специальные символы: @Code @Data @Result Символы @Code и @Data представляют текущие сегменты кода и данных. Они могут быть использованы только совместно с операторами SEG: asm mov ax,SEG @Data mov ds,ax end; Символ @Result представляет переменную с результатом функции внутри операторной части функции. Например, в функции function Sum(X, Y: Integer): Integer; begin Sum := X + Y; end; оператор, который назначает результат значения функции в Sum, будет использовать переменную @Result: function Sum(X, Y: Integer): Integer; begin asm mov ax,X add ax,Y mov @Result,AX end; end; Следующие символы не могут быть использованы в выражениях встроенного Ассемблера: - Стандартные процедуры и функции (например Writeln, Chr). - Специальные массивы Mem, MemW, MemL, Port, PortW. - Константы строковые, с плавающей точкой и типа множество. - Процедуры и функции, объявленные с директивой inline. - Метки, которые не объявлены в текущем блоке. - Символ @Result вне функции. Таблица 22.1 суммирует значение, класс и тип различных видов символов, которые могут использоваться в выражениях встроенного Ассемблера (типы и классы выражений описаны в следующем разделе). Таблица 22.1. Значения, классы и типы символов. ─────────────────────────────────────────────────────────────────── Символ Значение Класс Тип ─────────────────────────────────────────────────────────────────── Метка Адрес метки Память SHORT Константа Значение Непосредственный 0 константы Тип 0 Память Размер типа Поле Смещение поля Память Размер типа Переменная Адрес Память Размер типа переменной Процедура Адрес процедуры Память NEAR или FAR Функция Адрес функции Память NEAR или FAR Модуль 0 Непосредственный 0 @Code Адрес сегмента Память 0FFF0H кода @Data Адрес сегмента Память 0FFF0H данных @Result Смещение переменной Память Размер типа результата ─────────────────────────────────────────────────────────────────── Локальные переменные (переменные, объявленные в процедурах и функциях) всегда распределяются в стеке и используются относительно SS:BP, а значение локальной переменной - это его смещение со знаком от SS:BP. Ассемблер автоматически добавляет [BP] в ссылки на локальные переменные. Например, объявление procedure Test; var Count: Integer; и инструкция asm mov ax,Count end; ассемблируется в MOV AX, [BP-2] Встроенный Ассемблер всегда интерпретирует var параметр как 32 -битный указатель и размер var параметра всегда 4 (размер 32-битного указателя). В Паскале синтаксис для доступа к var параметру и параметру значению одинаков. Не так во встроенном Ассемблере. Поскольку var параметры в действительности указатели, Вы должны интерпретировать их так во встроенном Ассемблере. Так, чтобы обратиться к содержимому var параметра, Вы вначале загружаете 32-битный указатель, а затем обращаетесь к памяти, на которую он указывает. Например, если X и Y - var параметры функции Sum, то: function Sum(var X, Y: Integer): Integer; begin asm les bx,X mov ax,es:[bx] les bx,Y add ax,es:[bx] mov @Result,ax end; end; Некоторые символы, типы и переменные записей, позволяют обращаться к элементам структуры с использованием селектора ".". Например, для объявлений: type Point = record X, Y: Integer; end; Rect = record A, B: Point; end; var P: Point; R: Rect; следующие конструкции можно использовать для доступа к полям в переменных P и R: asm mov ax,P.X mov dx,P.Y mov cx,R.A.X mov bx,R.B.Y end; Идентификатор типа можно использовать для создания переменных "на лету". Каждая инструкция ниже генерирует машинный код, загружающий содержимое ES:[DI+4] в AX. asm mov ax,(Rect PTR es:[di]).B.X mov ax,Rect(es:[di]).B.X mov ax,es:Rect[di].B.X mov ax,Rect[es:di].B.X mov ax,es:[di].Rect.B.X end; Сфера действия символа типа поля или записи - это сфера действия записи или объекта этого типа. Кроме того, идентификатор модуля открывает сферу действия определенного модуля так же, как полный квалифицированный идентификатор в Паскале. Классы выражений. Встроенный Ассемблер делит выражения на 3 класса: регистры, ссылки на память и непосредственные значения. Выражение, которое состоит только из имени регистра - это регистровое выражение. Пример регистрового выражения - это AX, CL, DI и ES. Используемые как операнды, регистровые выражения прямо ассемблируются в инструкции, которые воздействуют на регистры процессора. Выражения, которые означают положение памяти, - это ссылки на память; метки, переменные, типированные константы и функции Паскаля относятся к этой категории. Выражения, которые не относятся к регистрам и не связаны с положением в памяти, - это непосредственные значения; эта группа включает нетипированные константы и типы Паскаля. Непосредственные значения и ссылки к памяти приводят к генерации различного кода, когда используются как операнды. Например: const Start = 10; var Count: Integer; ... asm mov ax,Start { MOV AX,xxxx } mov bx,Count { MOV BX,[xxxx] } mov cx,[Start] { MOV CX,[xxxx] } mov dx,OFFSET Count { MOV DX,xxxx } end; Поскольку Start - это непосредственное значение, первая MOV ассемблируется в инструкцию пересылки непосредственного значения. Вторая MOV транслируется в инструкцию пересылки памяти, т.к. Count - это ссылка на память. В третий MOV [] используются для преобразования Start в ссылку на память (в этом случае слово со смещением 10 в сегменте данных) и в четвертой MOV оператор OFFSET используется для преобразования Count в непосредственное значение (смещение Count в сегменте данных). Как Вы видите, [] и OFFSET дополняют друг друга. В терминах результирующего машинного кода следующий оператор asm идентичен двум первым строкам предыдущего оператора asm: asm mov ax,OFFSET [Start] mov bx,[OFFSET Count] end; Ссылки на память и непосредственные значения классифицируются как перемещаемые и абсолютные выражения. Перемещаемое выражение означает значение, которое требует перемещения во время редактирования, а абсолютное значение означает значение, которое не требует такого перемещения. Обычно выражения, которые ссылаются на метки, переменные, процедуры или функции, являются переместимыми, а выражения, которые ссылаются только на константы - абсолютные. Перемещение - это процесс, при котором редактор связей назначает символам абсолютные адреса. Во время компиляции компилятор не знает окончательных адресов меток, переменных, процедур или функций; они неизвестны до времени редактирования, когда редактор назначает абсолютные адреса символам. Встроенный Ассемблер позволяет Вам выполнить любую операцию над абсолютным значением, но ограничивает операции над перемещаемыми объектами до сложения и вычитания констант. Типы выражений. Каждое выражение встроенного ассемблера имеет тип - или более точно размер, поскольку встроенный асссемблер рассматривает тип выражения просто как размер его положения в памяти. Например, тип (размер) переменной Integer - 2, поскольку она занимает 2 байта. Встроенный асссемблер выполняет проверку типа, когда это возможно, так в инструкциях var QuitFlag: Boolean; OutBufPtr: Word; ... asm mov al,QuitFlag mov bx,OutBufPtr end; встроенный ассемблер проверяет что размер QuitFlag - 1 байт, а размер OutBufPtr - 2 байта. Если тип неправильный, то возникает ошибка; например, неверно: asm mov dl,OutBufPtr end; поскольку DL - регистр байтового размера, а OutBufPtr - слово. Тип ссылки на память может быть изменен с помощью приведения типа; корректный способ написания предыдущей инструкции: asm mov dl,BYTE PTR OutBufPtr mov dl,Byte(OutBufPtr) mov dl,OutBufPtr.Byte end; ссылаются на первый байт (наименее значащий) переменной OutBufPtr. В некоторых случаях ссылки на память нетипированные, т.е. не имеют типа. Пример с непосредственным значением, заключенным в []: asm mov al,[100H] mov bx,[100H] end; Встроенный асссемблер разрешает обе инструкции, поскольку выражение в [100H] не имеет типа - это означает "содержимое по адресу 100H в сегменте данных", и тип может быть определен из первого операнда (байт для AL, слово для BX). В случае если тип не может быть определен из другого операнда, встроенный Ассемблер требует явного приведения типов: asm inc BYTE PTR [100H] imul WORD PTR [100H] end; Таблица 22.2 суммирует предопределенные типы символов, которые встроенный Ассемблер предоставляет в дополнение к типа Паскаля. Таблица 22.2 Предопределенные типы символов. ──────────────────────────────── Символ Тип ──────────────────────────────── BYTE 1 WORD 2 DWORD 4 QWORD 8 TBYTE 10 NEAR 0FFFEH FAR 0FFFFH ──────────────────────────────── Заметим, что псевдотипы NEAR и FAR используются для указания модели вызова процедур и функций. Вы можете использовать NEAR и FAR в приведении типов точно так же, как другие символы. Например, если FarProc - FAR процедура procedure FarProc; far; и, если Вы пишете ассемблерный код в том же модуле, что и FarProc, Вы можете использовать более эффективный NEAR вызов: asm push cs call NEAR PTR FarProc end; Операторы выражений. Встроенный Ассемблер предоставляет операторы, разделенные на 12 классов по приоритетам. Таблица 22.3 приводит операторы встроенного Ассемблера в порядке уменьшения их приоритета. Таблица 22.3. Операторы выражений встроенного Ассемблера. ─────────────────────────────────────────────────────────────────── Оператор Комментарий ─────────────────────────────────────────────────────────────────── & Оператор перекрытия идентификатора () Селектор элемента структуры [] . HIGH LOW + - Унарные операторы : Оператор перекрытия сегмента OFFSET SEG TYPE PTR * / MOD SHL SHR + - Бинарные операторы NOT AND OR XOR Побитовые операторы ─────────────────────────────────────────────────────────────────── Примечание: Приоритет операторов встроенного Ассемблера отличается от Паскаля. Например, в ассемблерном выражении оператор AND имеет меньший приоритет, чем операторы + и -, а в Паскале наоборот. & Перекрытие идентификатора. Идентификатор, следующий за & интерпретируется как символ, определенный пользователем, даже если он совпадает с зарезервированным словом встроенного Ассемблера. (...) Подвыражение. Выражения внутри () вычисляются до интерпретации как элемент выражения. Другое выражение может предшествовать выражению внутри (); результат в этом случае становится суммой значений двух выражений с типом первого выражения. [...] Ссылка на память. Выражение внутри [] полностью вычисляется до интерпретации, как один элемент выражения. Выражение внутри [] может быть комбинировано с регистрами BX, BP, SI, DI, используя оператор + для указания индексации. Другое выражение может предшествовать выражению в []; результат становится суммой значений двух выражений с типом первого выражения. Результат всегда ссылка на память. "." Селектор элемента структуры. Результат - сумма выражения до точки и выражения после точки с типом выражения после точки. Символы, лежащие в сфере действия, определяемой выражением до точки, могут использоваться в выражении после точки. HIGH Возвращает старшие 8 бит выражения типа слово, следующего за оператором. Выражение должно быть абсолютным непосредственным значением. LOW Возвращает младшие 8 бит выражения типа слово, следующего за оператором. Выражение должно быть абсолютным непосредственным значением. "+" Унарный плюс. Возвращает выражение, следующее за +, без изменений. Выражение должно быть абсолютным непосредственным значением. "-" Унарный минус. Возвращает выражение, следующее за -, с отрицательным значением. Выражение должно быть абсолютным непосредственным значением. ":" Перекрытие сегмента. Указывает Ассемблеру, что выражение после ":" относится к сегменту с именем сегментного регистра (CS, DS, SS, ES) до ":". Результат - ссылка на память со значением выражения после ":". Когда перекрытие сегмента используется в операнде инструкции, инструкция будет предварена соответствующим префиксом перекрытия сегмента, гарантируя, что будет выбран указанный сегмент. OFFSET Возвращает смещение (младшее слово) выражения, следующего за оператором. Результат - непосредственное значение. SEG Возвращает сегментную часть (старшее слово) выражения, следующего за оператором. Результат - непосредственное значение. TYPE Возвращает тип (размер в байтах) выражения, следующего за оператором. Тип непосредственного значения - 0. PTR Оператор приведения типа. Результат - ссылка на память со значением выражения, следующего за оператором и типом выражения, стоящего перед оператором. "*" Умножение. Оба выражения должны быть абсолютными непосредственными значениями и результат - абсолютное непосредственное значение. / Целое деление. Оба выражения должны быть абсолютными непосредственными значениями и результат - абсолютное непосредственное значение. MOD Остаток от целого деления. Оба выражения должны быть абсолютными непосредственными значениями и результат - абсолютное непосредственное значение. SHL Логический сдвиг влево. Оба выражения должны быть абсолютными непосредственными значениями и результат - абсолютное непосредственное значение. SHR Логический сдвиг вправо. Оба выражения должны быть абсолютными непосредственными значениями и результат - абсолютное непосредственное значение. "+" Сложение. Выражения могут быть непосредственными значениями или ссылками к памяти, но только одно из выражений может быть перемещаемым значением. Если одно из выражений - перемещаемое значение, то результат так же перемещаемое значение. Если одно из выражений - ссылка на память, то результат так же ссылка на память. "-" Вычитание. Первое выражение может быть любого класса, а второе выражение должно быть абсолютным непосредственным значением. Результат того же класса, что и первое значение. NOT Побитовое отрицание. Выражение должно быть абсолютным непосредственным значением и результат - абсолютное непосредственное значение. AND Побитовое "и". Оба выражения должны быть абсолютными непосредственными значениями и результат - абсолютное непосредственное значение. OR Побитовое "или". Оба выражения должны быть абсолютными непосредственными значениями и результат - абсолютное непосредственное значение. XOR Побитовое исключающее "или". Оба выражения должны быть абсолютными непосредственными значениями и результат - абсолютное непосредственное значение. Ассемблерные процедуры и функции. До сих пор каждая конструкция asm...end была заключена в операторную часть begin...end. Ассемблерная директива Turbo Pascal позволяет Вам написать процедуру или функцию полностью на встроенном Ассемблере, не требуя операторной части begin...end. Пример ассемблерной функции: function LongMul(X, Y: Integer): Longint; assembler; asm mov ax,X imul Y end; Директива Ассемблера заставляет Turbo Pascal выполнить ряд оптимизаций при генерации кода: - Компилятор не генерирует код для копирования значения параметров в локальные переменные. Это действует на все значения параметров строкового типа и другие значения параметров с размером не 1, 2 или 4 байт. Внутри процедуры или функции такие параметры должны интерпретироваться, как если бы они были var параметрами. - Компилятор не распределяет переменную результата функции и ссылка на символ @Result является ошибкой. Строковые функции являются исключением из этого правила. Они всегда используют указатель @Result, который распределяется вызывающей программой. - Компилятор не генерирует кадра стека для процедур и функций без параметров и локальных переменных. - Автоматически генерируется код входа и выхода для ассемблерных процедур и функций, выглядящих как: push bp mov bp,sp sub sp,Locals ... mov sp,bp pop bp ret Params где Locals - размер локальных переменных, Params - размер параметров. Если Locals и Params ноль, то входного кода нет, а код выхода состоит из инструкции RET. Функции, использующие директиву Ассемблера, должны возвращать результаты: - Функции порядкового типа (Integer, Char, Boolean и перечислимые типы возвращают результаты в AL (8-битное значение), AX (16-битное значение), или DX:AX (32-битное значение). - Функции типа Real возвращают результат в DX:BX:AX - Функции с результатами типа 8087 (Single, Double, Extended и Comp) возвращают его в регистре ST(0) сопроцессора 8087. - Результат типа указатель возвращается в DX:AX. - Результат типа строка возвращается через временную память, на которую указывает @Result. Ассемблерная директива во многом сравнима с директивой external, а ассемблерные процедуры и функции должны следовать тем же правилам, что и external процедуры и функции. Следующие примеры демонстрируют некоторые отличия между операторами asm в обычных и ассемблерных фунциях. Первый пример использует оператор asm в обычной функции для преобразования строки в прописные буквы. Заметим, что значение параметра Str в этом случае ссылается на локальную переменную, поскольку компилятор автоматически генерирует входной код, который копирует действительный параметр в локальную память. function UpperCase(Str: String): String; begin asm cld lea si,Str les di,@Result SEGSS lodsb stosb xor ah,ah xchg ax,cx jcxz @3 @1: SEGSS lodsb cmp al,'a' ja @2 cmp a1,'z' jb @2 sub a1,20H @2: stosb loop @3: end; end; Второй пример - это ассемблерная версия функции UpperCase. В этом случае Str не копируется в локальную память, и функция должна интерпретировать Str как var параметр. function UpperCase(S: String): String; assembler; asm push ds cld lds si,Str les di,@Result lodsb stosb xor ah,ah xchg ax,cx jcxz @3 @1: lodsb cmp a1,'a' ja @2 cmp a1,'z' jb @2 sub a1,20H @2: stosb loop @1 @3: pop ds end;