ЭЛЕКТРОННАЯ БИБЛИОТЕКА КОАПП
Сборники Художественной, Технической, Справочной, Английской, Нормативной, Исторической, и др. литературы.



 

Часть 24

Часть 4. Использование Турбо Паскаля с языком Ассемблера

Глава 22. Встроенный Ассемблер

Встроенный Ассемблер Турбо Паскаля позволяет вам непосредственно в программах Паскаля записывать код Ассемблера для процессоров 8087/8087 и 80286/80287. Вы, можете конечно, если требуется, чередовать код Паскаля и Ассемблера, можете преобразовать код Асс
емблера в машинные инструкции вручную и воспользоваться затем операторами inline, либо выполнять компоновку с файлами .OBJ, которые содержат внешние процедуры и функции (external).
     Встроенные операторы Ассемблера представляют собой большое подмножество синтаксиса, поддерживаемого Турбо Ассемблером и Макроассемблером фирмы Microsoft. Встроенный Ассемблер поддерживает все коды операций процессором 8086/8087 и 80286/80287 и некот
орые из операций, используемых в выражениях Турбо Ассемблера. 
     За исключением директив DB (определить байт), DW (определить слово) и DD (определить двойное слово) никакие другие директивы Турбо Ассемблера, типа EQU, STRUC, SEGMENT или MACRO, встроенным Ассемблером не поддерживаются. Однако, операции, реализуемы
е с помощью директив Турбо Ассемблера, близко соответствуют конструкциям Турбо Паскаля. Например, большинство директив EQU соответствуют описаниям Турбо Паскаля const, var и type, директива PROC - описаниям procedure и function, а директива STRUC - типам
 record Турбо Паскаля. Фактически, встроенный Ассемблер Турбо Паскаля можно рассматривать, как компилятор языка Ассемблера, использующий для всех описаний синтаксис Паскаля. 
                          Оператор asm

     Встроенный Ассемблер становится доступным с помощью операторов asm. Оператор asm имеет следующий синтаксис: 
   asm оператор_Ассемблера < разделитель оператор_Ассемблера > end
 где "оператор_Ассемблера" представляет собой оператор языка Ассемблера, а "разделитель " - это точка с запятой, новая строка или комментарий Паскаля. Приведем некоторые примеры операторов 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             { для чтения клавиши вызвать BIOS }
          mov   CharCode,al     { сохранить код ASCII }
          mov   ScanCode,ah     { сохранить код опроса }
         end;

         asm
          push  ds              { сохранить DS }
          lds   si,Source       { загрузить указатель источника }
          les   di,Dest         { загрузить указатель приемника }
          mov   cx,Count        { загрузить размер блока }
          cld                   { переместить }
          rep   movsb           { скопировать блок }
          pop   ds              { восстановить DS }
        end;

     Заметим, что на одной строке можно разместить несколько операторов Ассемблера, разделив их точками с запятой. Кроме того следует отметить, что если операторы Ассемблера размещаются на разных строках, разделять их точками с запятой не требуется. Заме
тим также, что точка с запятой не говорит о том, что остальная часть строки представляет собой комментарий. Комментарии следует записывать, используя синтаксис Паскаля: с помощью { и } или (* и *). 
                     Использование регистров

     Правила использования регистров в операторе asm в основном совпадают с этими правилами для внешних процедур и функций. Оператор asm должен сохранять регистры BP, SP, SS и DS, но может свободно изменять AX, BX, CX, DX, SI, DI, ES и регистр флагов. На
 входе в оператор asm регистр BP указывает на текущий кадр стека, SP указывает на вершину стека, регистр SS содержит адрес сегмента стека, а DS - адрес сегмента данных. За исключением регистров BP, SP, SS и DS, оператор asm не может делать никаких предпо
ложений относительно содержимого регистров на входе в этот оператор. 
                 Синтаксис операторa Ассемблера

     Оператор Ассемблера имеет следующий синтаксис:
 [ метка":" ] < префикс > [код_операции [операнд < "," операнд >]]  где "метка" - это идентификатор метки, "префикс" - префикс кода операции Ассемблера. "Код_операции" - код инструкции или директива Ассемблера, а "операнд" - выражение Ассемблера. 
     Между операторами Ассемблера (но не в них) допускается включать комментарии. Допустимо, например, следующее: 
     asm
       mov   ax,1                 { начальное значение }
       mov   cx,100               { счетчик }
     end;
 однако следущая запись ошибочна: 
     asm
       mov   { начальное значение }  ax,1
       mov   cx, { счетчик } 100
     end;

                              Метки

     Метки в Ассемблере определяются также, как в Паскале: перед оператором записывается идентификатор метки и двоеточие. Как и в Паскале, метки в Ассемблере должны описываться в объявлении label того блока, который содержит оператор asm. Однако из этого
 правила есть одно исключение. Это локальные метки. 
     Локальные метки - это метки, которые начинаются с символа @. Поскольку этот символ не может быть частью идентификатора Паскаля, такие локальные метки автоматически ограничиваются использованием их в операторах 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     повторение строковой операции, пока равно/пока ноль
  REPNE/REPNZ   повторение строковой операции, пока не равно/пока
                не ноль
  SEGCS         переопределение сегмента CS (сегмента кода)
  SEGDS         переопределение сегмента DS (сегмента данных)
  SEGES         переопределение сегмента ES (дополнительного
                сегмента)
  SEGSS         переопределение сегмента SS (сегмента стека)
  ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД

     Перед инструкцией Ассемблера может указываться 0 или более этих префиксов. Например: 
     asm
       rep movsb            { переместить CX байт из DS:SI в ES:DI }
       SEGES lodsw          { загрузить слово из ES:SI }
       SEGES mov ax,[bx]    { то же, что MOV AX,CS:[BX] }
       SEGES                { влияет на следующий оператор Ассемблера }
       mov WORD PTR [DI},0  { становится MOV WORD PTR ES:[DI],0 }

     Заметим, что префикс операции можно задавать без кода инструкции в данной строке. В этом случае префикс операции влияет на следующий оператор Ассемблера. 
     Коды инструкции редко имеют более одного префикса. В общем случае имеет смысл не более трех префиксов (LOCK, затем SEGxx, затем REPxx). Несколько префиксов следует использовать аккуратно, учитывая их порядок. Некоторые процессоры 80х86 не могут корр
ектно обрабатывать все их сочетания. Например, если в процессе повторения строковой инструкции происходит прерывание, процессор 8086 или 8088 "помнит" только префикс REPxx, поэтому префиксы LOCK или SEGxx перед префиксом REPxx лучше не указывать. 
                         Коды инструкций

     Встроенный Ассемблер поддерживает инструкции процессоров 8086/8087 и 80286/80287. Инструкции процессора 8087 доступны только в состоянии {$N+} (разрешено использование арифметического сопроцессора), а инструкции процессора 80286 - только в состоянии
 {$G+} (разрешена генерация кода для процессора 80286), а инструкции сопроцессора 80287 - только в состоянии {$G+,N+}. 
     Полное описание каждой инструкции содержится в справочных материалах по процессорам 80х86 и 80х87. 
                      Размер инструкции RET

     Инструкция REP генерирует код машинной инструкции возврата ближнего (NEAR) или дальнего (FAR) типа, в зависимости от модели вызова текущей процедуры или функции. 
     procedure NearProc; near;
     begin
       asm
         ret      { генерируется ближний возврат }
       end;
     end;

     procedure FarProc; far
     begin
       asm
         ret      { генерируется дальний возврат }
       end;
     end;

     С другой стороны, инструкции RETN и RETF всегда генерируют ближний или дальний возврат соответственно, независимо от модели вызова текущей процедуры или функции. 
           Автоматическое определение размера перехода

     Если не указывается противное, встроенный Ассемблер оптимизирует инструкции перехода, автоматически выбирая наиболее короткую, и, следовательно, наиболее эффективную форму инструкции перехода. Такое автоматическое определение размера перехода примен
яется к инструкции безусловного перехода (JMP) и всем инструкциям условного перехода, когда переход выполняется на метку, а не на процедуру или функцию. 
     Для инструкции безусловного перехода встроенный Ассемблер генерирует короткий переход (один байт кода операции, за которым следует один байт смещения), если расстояние до целевой метки находится в границах от -128 до 127 байт. В противном случае ген
ерируется ближний переход (один байт кода операции, за которым следуют два байта смещения). 
     Для инструкций условного перехода короткий переход (один байт кода операции, за которым следует один байт смещения) генерируется, если расстояние до целевой метки находится в пределах от -128 до 127 байт, в противном случае встроенный Ассемблер гене
рирует короткий переход с обратным условием, который выполняет переход на целевую метку через ближний переход (в общем случае 5 байт). Например, оператор Ассемблера: 
     JC    Stop
 где метка Stop не находится в границах короткого перехода, преобразуется в последовательность машинных кодов, соответствующих инструкциям: 
     jnc   Skip
     jmp   Stop
     Skip:

     Переходы на точки входа в процедуру или функцию всегда имеют ближний или дальний тип (но не короткий), а условные переходы на процедуру или функцию не допускаются. Вы можете указать встроенному Ассемблеру, что нужно генерировать ближний или дальний 
переход, используя конструкцию NEAR PTR или FAR PTR. Например, операторы Ассемблера: 
     jmp      NEAR PTR Stop
     jmp      FAR PTR Stop
 будут всегда генерировать соответственно ближний и дальний переход, даже если на метку Stop можно перейти с помощью короткого перехода. 
                      Директивы Ассемблера

     Встроенный Ассемблер Турбо Паскаля поддерживает три директивы Ассемблера: DB (определить байт), DW (определить слово) и DD (определить двойное слово). Каждая из них генерирует данные, соответствующие разделенным запятым операндам, которые следуют за
 директивой. 
     Директива DB генерирует последовательность байт. Каждый операнд может представлять собой выражение-константу со значением от -128 до 255, или строку символов любой длины. Выражение-константа генерирует 1 байт кода, а строки генерируют последовательн
ость байт со значениями, соответсвующим коду ASCII каждого символа. 
     Директива DW генерирует последовательность слов. Каждый операнд может представлять собой выражение-константу со значением от -32768 до 65535, или адресное выражение. Для адресного выражения встроенный Ассемблер генерирует указатель ближнего типа, то
 есть слово, содержащие смещения адреса. 
     Директива DD герерирует последовательность двойных слов. Каждый операнд может представлять собой выражение-константу со значением от -2147483648 до 4294967295 или адресное выражение. Для адресного выражения встроенный Ассемблер генерирует указатель 
дальнего типа, то есть слово, содержащие смещения адреса, за которым следует слово, содержащее сегментную часть адреса. 
     Данные, генерируемые по директивам DB, DW и DD, всегда записываются в сегмент кода, аналогично коду, генерируемому другими операторами встроенного Ассемблера. Чтобы сгенерировать инициализированные или неинициализированные данные в сегменте данных, 
вам следует использовать обычные описания Паскаля типа var или const. 
     Приведем некоторые примеры директив DB, DW и DD:

     asm
       DB      00FH                                   { 1 байт }
       DB      0,99                                  { 2 байта }
       DB      'A'                                   { Ord('A) }
       DB      'Пример',0DH,OAH              { строка, за которой
                      следуют возврат каретки и перевод строки }
       DB      12,"Turbo Pascal"              { строка Паскаля }
       DW      0FFFFH                                { 1 слово }
       DW      0,9999                                { 2 слова }
       DW      'A'                     { эквивалентно DB 'A',0 }
       DW      'BA'                  { эквивалентно DB 'A','B' }
       DW      MyVar                          { смещение MyVar }
       DW      MyProc                        { смещение MyProc }
       DD      0FFFFFFFH                     { 1 двойное слово }
       DD      0,99999999                    { 2 двойных слова }
       DD      'A'                 { эквивалентно DB 'A',0,0,0 }
       DD      'DBCA'        { эквивалентно DS 'A','B','C','D' }
       DD      MyVar                      { указатель на MyVar }
       DD      MyProc                    { указатель на MyProc }
     end;

     В Турбо Ассемблере указание перед идентификатором директивы DB, DW или DD приводит к генерации в том месте, где указана директива, переменной размером в байт, слово или двойное слово. Например, Турбо Ассемблер допускает следующее: 
     ByteVar        DB          ?
     WordVar        DW          ?
     ...
                    mov         al,ByteVar
                    mov         bx,WordVar

     Встроенный Ассемблер не поддерживает такие описания переменных. В Турбо Паскале единственным видом идентификатора, который можно определить в операторе встроенного Ассемблера, является метка. Все переменные должны описываться с помощью синтаксиса Па
скаля, и предыдущая конструкция соответствует следующему: 
     var
       ByteVar:    Byte;
       WordWat:    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          WQORD        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. Корректной конструкцией встроенного Ассемблера в этом случае будет: 
     asm
       mov     ax,X
       add     ax,Y
       mov     Z,ax
     end;

     Другим важным отличием выражений Паскаля и встроенного Ассемблера является способ интерпретации переменных. В выражении Паскаля ссылка не переменную интерпретируется, как содержимое переменной, но в выражении встроенного Ассемблера ссылка на перемен
ную означает адрес переменной. Например, в Паскале выражение X + 4, где X - переменная, означает содержимое X, плюс 4, а во встроенном Ассемблере это означает содержимое в слове по адресу на 4 байта выше, чем адрес X. Поэтому, хотя допустима запись: 
     asm
       mov      ax,X+4
     end;
 этот код не загружает значения X плюс 4 в регистр AX, а загружает значение слова, записанного через 4 байта после X. Корректной записью сложения 4 с содержимым X будет: 
     asm
       MOV   AX,X
       ADD   AX,4
     end;

                       Элементы выражений

     Основными элементами выражения являются константы, регистры и идентификаторы. 
                            Константы

     Встроенный Ассемблер поддерживает два типа констант: числовые константы и строковые константы. 
                       Числовые константы

     Числовые константы должны быть целыми и принимать значения в диапазоне от -2147483648 до 4294967295. 
     По умолчанию числовые константы являются десятичными, однако встроенный Ассемблер поддерживает также двоичные, восьмеричные и шестнадцатиричные константы. Двоичное представление обозначается записью после числа буквы B, восьмеричное - записью буквы 
O, а шестнадцатиричное - записью после числа H или указанием перед числом символа $. 
     В выражениях Паскаля суффиксы B, O и H не поддерживаются. Выражения Паскаля допускают только десятичную (по умолчанию) и шестнадцатиричную запись (используется префикс $). 
     Числовые константы должны начинаться с одной из цифр или символа $. Таким образом, когда вы записываете шестнадцатиричную константу с помощью суффикса H, то если первой значащей цифрой является одна из шестнадцатиричных цифр от A до F, то требуется 
указать дополнительный ноль. Например, 0BAD4H и $BAD4 представляют собой шестнадцатиричные константы, а BAD4H - это идентификатор, так как он начинается с буквы, а не с цифры. 
                       Строковые константы

     Строковые константы должны заключаться в одиночные или двойные кавычки. Указание двух последовательных кавычек одного типа в качестве закрывающих кавычекю считается за один символ. Приведем некоторые примеры строковых констант: 
     'Z'
     'Turbo Pascal'
     "That's all folks"
     "That's all falks," he said.'
     '100
     '"'
     "'"

     Заметим, что в четвертой строке для обозначения одиночного символа двойных кавычек используется две последовательных одиночных кавычки. 
     В директивах DB допускаются строковые константы любой длины. Это приводит к выделению последовательности байт, содержащих значения (ASCII) символов строки. Во всех других случаях строковые константы не могут превышать четырех символов и обозначают ч
исловое значение, которое может участвовать в выражениях. Числовое значение строки вычисляется следующим образом: 
    Ord(Ch1) + Ord(Ch2) shl 8 + Ord(Ch3) shl 16 + Ord(Ch4) shl 24
 где Ch1 - это самый правый (последний) символ, а Ch4 - самый левый (первый) символ. Если строка короче 4 символов, то самые левые (первые) символы считаются нулевыми. Приведем некоторые примеры строковых констант и их значений: 
     'a'             00000061H
     'ba'            00006261H
     'cba'           00636261H
     'dcba'          64636261H
     'a'             00006120H
     ' a'            20202061H
     'a'*2           000000E2H
     'a'-'A'         00000020H
     not 'a'         FFFFFF9EH

                            Регистры

     Следующие зарезервированные идентификаторы обозначают регистры ЦП: 
   ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД
   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) могут использоваться вместе с операцией переопределения сегмента (:) и указывать на другой сегмент, отличный от того, который процессор выбирает по умолчанию. 
     Идентификатор ST обозначает верхний регистр стека регистров с плавающей точкой сопроцессора 8087. На каждый из 8 регистров с плавающей точкой можно ссылаться с помощью ST(x), где x - константа от 0 до 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                      Память           Размер типа
 Поле          Смещение поля          Память           Размер типа
 Переменная    Адрес переменной       Память           Размер типа
 Процедура     Адрес процедуры        Память          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]. 
     Встроенный Ассемблер всегда интерпретирует параметр-переменную, как 32-разрядный указатель, а размер параметра-переменной всегда равен 4 (размеру 32-разрядного указателя). В Паскале синтаксис для доступа к параметру-переменной и к значению параметра
 одинаков. В случае встроенного Ассемблера это не так. Поэтому для доступа к содержимому параметра-переменной вам сначала придется загрузить 32-разрядный указатель, а затем обратиться к ячейке, на которую он указывает. Например, если X и Y - параметры-пе
ременные приведенной выше функции 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;

     Для непосредственного построения переменной можно использовать идентификатор типа. Каждая из приведенных ниже инструкций генерирует один и тот же машинный код, загружающий в AX ES:[DI+4]: 
     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;

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

     Выражения встроенного Ассемблера подразделяются на три класса: регистровые значения, ссылки на память и непосредственные значения. 
     Выражение, состоящее только из имени регистра, является регистровым значением. Примерами регистровых значений являются 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 для преобразования Count в непосредственное значение (смещение Count в се
гменте данных) используется операция OFFSET. 
     Как вы можете видеть, квадратные скобки и операция OFFSET дополняют друг друга. В терминах результирующего машинного кода следующий оператор asm идентичен первым двум строкам предыдущего оператора asm: 
     asm
       mov       ax,OFFSET [Start]
       mov       bx,[OFFSET Count]
     end;

     Ссылки на память и непосредственные значения классифицируются, в свою очередь, как перемещаемые и абсолютные выражения. Перемещаемое выражение обозначает значение, которое требует на этапе компоновки перемещения, а абсолютное выражение обозначает зн
ачение, которое такого перемещения не требует. Обычно выражение со ссылкой на метку, переменную процедуру или функцию является перемещаемым, а выражение, где операции выполняются исключительно с константами - абсолютным. 
     Перемещение является процессом, с помощью которого компоновщик присваивает идентификаторам абсолютные адреса. На этапе компоновки компилятору неизвестны конечные адреса метки, переменной, процедуры или функции. Ини не будут известны до этапа компоно
вки, на котором компоновщик присваивает идентификатору конкретный абсолютный адрес. 
     Встроенный Ассемлер позволяет вам выполнять любую операцию с абсолютным значением, но операции с перемещаемыми значениями ограничиваются сложением и вычитанием констант. 
                         Типы выражений

     Каждое выражение встроенного Ассемлера имеет соответствующий тип, или, если говорить точнее, размер, поскольку встроенный Ассемлер рассматривает тип выражения просто как его размер в памяти. Например, тип (размер) переменной Integer равен 2, так как
 она занимает два байта. 
     Там, где это возможно, встроенный Ассемлер выполняет проверку типов, поэтому в инструкциях: 
     var
       QuitFlag: Boolean;
       OutBufPtr: Word;
     ...
     asm
       mov       al,QuitFlag
       mov       bx,OutBufPtr
     end;
 встроенный Ассемблер проверяет, что размер QuitFlag равен 1 (байт), а размер OutBufPtr - двум (слово). Если проверка типа обнаруживает несоответствие, возникает ошибка. Например, следующее недопустимо: 
     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
       mov     BYTE PTR [100H]
       mov     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 аналогично другим идентификаторам. Например, если FarPr
oc - процедура с дальним типом вызова (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                 Поразрядные операции.
 ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД

     Операция &

     Переопределение идентификатора. Идентификатор, непосредственно следующий за амперсандом, интерпретируется как идентификатор, определяемый пользователем, даже если он соответствует зарезервированному слову встроенного Ассемблера. 
     Операция (...)

     Подвыражение. Выражение в скобках полностью вычисляется, после чего интерпретируется, как один элемент. Выражению в скобках может предшествовать другое выражение. Результатом в этом случае будет сумма значений двух выражений с типом первого выражени
я. 
     Операция [...]

     Ссылка на память. Выражение в квадратных скобках полностью вычисляется, после чего интерпретируется, как один элемент. Выражение в квадратных скобках может комбинироваться с регистрами BX, BP, SI, DI с помощью операции +, что указывает на индексиров
ание регистра ЦП. Выражению в квадратных скобках может предшествовать другое выражение. Результатом в этом случае будет сумма значений двух выражений с типом первого выражения. Результатом всегда будет ссылка на память. 
     Операция .

     Выбор элемента структуры. Результатом будет сумма выражения перед точкой и выражения после точки с типом выражения после точки. Идентификаторы, относящиеся к области действия и указанные в выражении перед точкой, доступны в выражении после точки. 
     Операция HIGH

     Возвращает старшие 8 бит выражения размером в слово, следущего за операцией. Выражение должно представлять собой непосредственное абсолютное значение. 
     Операция LOW

     Возвращает младшие 8 бит выражения размером в слово, следущего за операцией. Выражение должно представлять собой непосредственное абсолютное значение. 
     Операция +

     Унарный плюс. Возвращает следующее за плюсом выражение без изменений. Выражение должно представлять собой непосредственное абсолютное значение. 
     Операция -

     Унарный минус. Возвращает следующее за минусом выражение с обратным знаком. Выражение должно представлять собой непосредственное абсолютное значение. 
     Операция :

     Переопределение сегмента. Указывает Ассемблеру, что выражение после двоеточия относится к сегменту, заданному именем сегментного регистра (CS, DS, SS или ES) перед двоеточием. Результатом является ссылка на память со значением выражения после двоето
чия. Когда переопределение сегмента используется в операнде инструкции, инструкции предшествует соответствующий префикс переопределения сегмента, обеспечивающий выбор указанного сегмента. 
     Операция OFFSET

     Возвращает смещение следующего за операцией выражения (младшее слово). Результатом будет непосредственное значение. 
     Операция SEG

     Возвращает сегмент следующего за операцией выражения (старшее слово). Результатом будет непосредственное значение. 
     Операция TYPE

     Возвращает тип (размер в байтах) следующего за операцией выражения. Типом непосредственного значения будет 0. 
     Операция PTR

     Операция назначения типа. Результатом будет ссылка на память со значением выражения, следующего за операцией и типом выражения перед операцией. 
     Операция *

     Умножение. Оба выражения должны представлять собой непосредственные абсолютные значения. Результатом будет непосредственное абсолютное значение. 
     Операция /

     Целочисленное деление. Оба выражения должны представлять собой непосредственные абсолютные значения. Результатом будет непосредственное абсолютное значение. 
     Операция MOD

     Остаток целочисленного деления. Оба выражения должны представлять собой непосредственные абсолютные значения. Результатом будет непосредственное абсолютное значение. 
     Операция SHL

     Логический сдвиг влево. Оба выражения должны представлять собой непосредственные абсолютные значения. Результатом будет непосредственное абсолютное значение. 
     Операция SHR

     Логический сдвиг вправо. Оба выражения должны представлять собой непосредственные абсолютные значения. Результатом будет непосредственное абсолютное значение. 
     Операция +

     Сложение. Выражения могут представлять собой непосредственные абсолютные значения или ссылки на память, но перемещаемым значением может быть только одно выражение. Если одно из выражений - перемещаемое значение, то результатом также будет перемещаем
ое значение. Если одно из выражений - ссылка на память, то результатом также будет ссылка на память. 
     Операция -

     Вычитание. Первое выражение может иметь любой класс, а второе выражение должно быть непосредственным абсолютным выражением. Результат имеет тот же тип, что и первое выражение. 
     Операция NOT

     Порязрядное отрицание. Выражение должно представлять собой непосредственные абсолютные значения. Результатом будет непосредственное абсолютное значение. 
     Операция AND

     Поразрядная операция AND (И). Оба выражения должны представлять собой непосредственные абсолютные значения. Результатом будет непосредственное абсолютное значение. 
     Операция OR

     Поразрядная операция OR (ИЛИ). Оба выражения должны представлять собой непосредственные абсолютные значения. Результатом будет непосредственное абсолютное значение. 
     Операция XOR

     Поразрядная операция XOR (исключающее ИЛИ). Оба выражения должны представлять собой непосредственные абсолютные значения. Результатом будет непосредственное абсолютное значение. 
                 Процедуры и функции Ассемблера


     До сих пор мы рассматривали конструкцию asm...end, как оператор с обычной частью begin...end. Директива assembler в Турбо Паскале позволяет вам писать на встроенном Ассемблере целиком процедуры и функции без необходимости begin...end. Приведем приме
р функции на Ассемблере: 
     function LongMul(X, Y: Integer) : Longint; assembler;
     asm
       mov            ax,X
       imul           Y
     end;

     Директива assembler приводит к тому, что Турбо Паскаль выполняет при генерации кода следующую оптимизацию: 
     - Компилятор не генерирует код для копирования параметров-значений в локальные переменные. Это влияет на все параметры-значения строкового типа и другие значения-параметры, размер которых не равен 1, 2 или 4 байтам. Внутри процедуры или функции таки
е параметры должны интерпретироваться, как если бы они были параметрами-переменными. 
     - Компилятор не выделяет память для результата функции, и ссылка на идентификатор @Result будет ошибкой. Однако строковые функции являются исключением из этого правила - они всегда имеют указатель @Result, который распределяется пользователем. 
     - Для процедур и функций, не имеющих параметров и локальных переменных, компилятор не генерирует кадр стека. 
     - Для процедуры и функции на Ассемблере автоматически генерируется код выхода: 
         push   bp        ; присутствует, если Locals <> 0 или
                          ; Params <> 0
         mov    bp,sp     ; присутствует, если Locals <> 0 или
                          ; Params <> 0
         sub    sp,Locals ; присутствует, если Locals <> 0
         ...
         mov    sp,bp     ; присутствует, если Locals <> 0
         pop    bp        ; присутствует, если Locals <> 0 или
                          ; Params <> 0
         ret    Params    ; всегда присутствует
 где Locals - размер локальных переменных, а Params - размер параметров. Если и Locals и Params = 0, то кода входа не будет, и код выхода состоит просто из инструкции RET. 
     Функции, использующие директиву assembler, должны возвращать результат следующим образом: 
     - результаты функции порядкового типа (Integer, Char, Boolean, перечислимые типы) возвращаются в AL (8-разрядное значение), AX (16-разрядное значение) или DX:AX (32-разрядное значение); 
     - результаты функции вещественного типа (Real) возвращаются в DX:BX:AX; 
     - результаты функции типов 8087 (Single, Double, Extended, Comp) возвращаются в ST(0) (регистр стека сопроцессора 8087); 
     - результаты функции типа указатель возвращаются в DX:AX (32-разрядные значения): 
     - результаты функции строкового типа возвращаются во временной ячейке, на которую указывает @Result. 
     Директива assembler во многом похожа на директиву 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        al,'a'
         ja         @2
         cmp        al,'z'
         jb         @2
         sub        al,20H
     @2:
         stosb
         loop       @1
     @3:
      end;
     end;

     Второй пример на Ассемблере представляет собой версию функции UpperCase. В этом случае Str не копируется в локальную память, и фунцкция должна интерпретировать Str, как параметр-переменную. 
     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         al,'a'
       ja          @2
       cmp         al,'z'
       jb          @2
       sub         al,20H
     @2:
       stosb
       loop       @1
     @3:
       pop        ds
     end;


Яндекс цитирования