Лекция 11. Макросредства языка ассемблера (2 пары)



  • Понятие о макросредствах языка ассемблера
  • Псевдооператоры equ и =
  • Макрокоманды
  • Макродирективы
  • Директивы условной компиляции

Любопытный читатель к этому занятию, вероятно, попытался самостоятельно написать хотя бы несколько программ на ассемблере.

Скорее всего, эти программы были предназначены для решения небольших, чисто исследовательских задач, но даже на примере этих маленьких по объему программ вам, наверное, стали очевидны некоторые из перечисленных здесь проблем:

  • плохое понимание исходного текста программы, особенно по прошествии некоторого времени после ее написания;
  • ограниченность набора команд;
  • повторяемость некоторых идентичных или незначительно отличающихся участков программы;
  • необходимость включения в каждую программу участков кода, которые уже были использованы в других программах;
  • и многое другое

Если бы мы писали программу на машинном языке, то данные проблемы были бы принципиально не решаемыми. Но язык ассемблера, являясь символическим аналогом машинного языка, предоставляет для их решения ряд средств.

Основной целью, которая при этом преследуется, является повышение удобства написания программ.

В общем случае эта цель достигается по нескольким направлениям за счет следующего:

  • расширения набора директив;
  • введения некоторых дополнительных команд, не имеющих аналогов в системе команд микропроцессора. За примером далеко ходить не нужно — команды setfield и getfield, которые скрывают от программиста рутинные действия и генерируют наиболее эффективный код;
  • введения сложных типов данных.

Но это все глобальные направления, по которым развивается сам транслятор от версии к версии.

Что же делать программисту для решения его локальной задачи, для облегчения работы в определенной проблемной области?

Для этого разработчики компиляторов ассемблера включают в язык и постоянно совершенствуют аппарат макросредств. Этот аппарат является очень мощным и важным.

В общем случае есть смысл говорить о том, что транслятор ассемблера состоит из двух частей — непосредственно транслятора, формирующего объектный модуль, и макроассемблера (рис. 1).

Рис. 1. Макроассемблер в общей схеме трансляции программы на TASM

Если вы знакомы с языком С или С++, то конечно помните широко применяемый в них механизм препроцессорной обработки. Он является некоторым аналогом механизма заложенного в работу макроассемблера. Для тех, кто ничего раньше не слышал об этих механизмах, поясню их суть.

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

Таким образом, обработка программы на ассемблере с использованием макросредств неявно осуществляется транслятором в две фазы(рис. 1).

На первой фазе работает часть компилятора, называемая макроассемблером, функции которого на идейном уровне мы описали чуть выше.

На второй фазе трансляции работает непосредственно ассемблер, задачей которого является формирование объектного кода, содержащего текст исходной программы в машинном виде.

Далее мы обсудим основной набор макросредств, доступных при использовании компилятора TASM. Отметим, что большинство этих средств доступно и в компиляторе с языка ассемблера фирмы Microsoft.

Обсуждение начнем с простейших средств и закончим более сложными.

Псевдооператоры equ и =

К простейшим макросредствам языка ассемблера можно отнести псевдооператоры equ и "=" (равно).

Их мы уже неоднократно использовали при написании программ.

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

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

Синтаксис псевдооператора equ:

имя_идентификатора    equ     строка или числовое_выражение

Синтаксис псевдооператора “=”:

имя_идентификатора    =          числовое_выражение

Несмотря на внешнее и функциональное сходство псевдооператоры equ и “=” отличаются следующим:

  • из синтаксического описания видно, что с помощью equ идентификатору можно ставить в соответствие как числовые выражения, так и текстовые строки, а псевдооператор “=” может использоваться только с числовыми выражениями;
  • идентификаторы, определенные с помощью “=”, можно переопределять в исходном тексте программы, а определенные с использованием equ — нельзя.

Ассемблер всегда пытается вычислить значение строки, воспринимая ее как выражение. Для того чтобы строка воспринималась именно как текстовая, необходимо заключить ее в угловые скобки: <строка>.

Кстати сказать, угловые скобки являются оператором ассемблера, с помощью которого транслятору сообщается, что заключенная в них строка должна трактоваться как текст, даже если в нее входят служебные слова ассемблера или операторы. Хотя в режиме Ideal это не обязательно, так как строка для equ в нем всегда трактуется как текстовая.

Псевдооператор equ удобно использовать для настройки программы на конкретные условия выполнения, замены сложных в обозначении объектов, многократно используемых в программе, более простыми именами и т. п.

К примеру:

masmmodel smallstack  256mas_size equ     10      ;размерность массиваakk     equ     ax                 ;переименовать регистрmas_elem          equ     mas[bx][si]          ;адресовать элемент массива.data;описание массива из 10 байт:mas    db      mas_size dup (0).code          mov   akk,@data          ;фактически mov ax,@data          mov   ds,akk           ;фактически mov ds,ax...          mov   al,mas_elem          ;фактически — mov al,mas[bx][si]

Псевдооператор “=” удобно использовать для определения простых абсолютных (то есть не зависящих от места загрузки программы в память) математических выражений.

Главное условие то, чтобы транслятор мог вычислить эти выражения во время трансляции.

К примеру:

.dataadr1   db      5 dup (0)adr2   dw     0len = 43len = len+1        ;можно и так, через предыдущее определениеlen = adr2-adr1

Как видно из примера, в правой части псевдооператора “=” можно использовать метки и ссылки на адреса — главное, чтобы в итоге получилось абсолютное выражение.

Компилятор TASM, начиная с версии 3.00, содержит директивы, значительно расширяющие его возможности по работе с текстовыми макросами. Эти директивы аналогичны некоторым функциям обработки строк в языках высокого уровня. Под строками здесь понимается текст, описанный с помощью псевдооператора equ.

Набор этих директив следующий:

  • директива слияния строк catstr:
  • идентификатор catstr строка_1,строка_2,... — значением этого макроса будет новая строка, состоящая из сцепленной слева направо последовательности строк строка_1,строка_2,...
  • В качестве сцепляемых строк могут быть указаны имена ранее определенных макросов.
  • К примеру:
pre     equ     Привет,name  equ     < Юля>privet catstr  pre,name ;privet= “Привет, Юля”
  • директива выделения подстроки в строке substr:
  • идентификатор substr строка,номер_позиции,размер — значением данного макроса будет часть заданной строки, начинающаяся с позиции с номером номер_позиции и длиной, указанной в размер.
  • Если требуется только остаток строки, начиная с некоторой позиции, то достаточно указать только номер_позиции без указания размера.
  • К примеру:
;продолжение предыдущего фрагмента:privet catstr  pre,name ;privet= “Привет, Юля”name  substr privet,7,3          ;name=“Юля”
  • директива определения вхождения одной строки в другую instr:
  • идентификатор instr номер_нач_позиции,строка_1,строка_2 — после обработки данного макроса транслятором идентификатору будет присвоено числовое значение, соответствующее номеру (первой) позиции, с которой совпадают строка_1 и строка_2.
  • Если такого совпадения нет, то идентификатор получит значение 0;
  • директива определения длины строки в текстовом макросе sizestr:
  • идентификатор sizestr строка — в результате обработки данного макроса значение идентификатор устанавливается равным длине строки.
;как продолжение предыдущего фрагмента:privet catstr  pre,name ;privet= “Привет, Юля”len      sizestr privet ;len=10

Эти директивы очень удобно использовать при разработке макрокоманд, которые являются следующим макросредством, предоставляемым компилятором ассемблера.

Макрокоманды

Идейно макрокоманда представляет собой дальнейшее развитие механизма замены текста.

С помощью макрокоманд в текст программы можно вставлять последовательности строк (которые логически могут быть данными или командами) и даже более того — привязывать их к контексту места вставки.

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

Листинг 1. Пример программы на ассемблере<1> ;---------Prg_3_1.asm----------------------------------<2> ;Программа преобразования двузначного шестнадцатеричного числа<3> ;в символьном виде в двоичное представление.<4> ;Вход: исходное шестнадцатеричное число из двух цифр,<5> ;вводится с клавиатуры.<6> ;Выход: результат преобразования должен<7> ;быть в регистре al.<8> ;------------------------------------------------------<9> data segment para public 'data' ;сегмент данных<10> message   db      'Введите две шестнадцатеричные цифры,$'<11> data ends<12> stk segment stack<13> db      256 dup ('?')      ;сегмент стека<14> stk ends<15> code segment para public 'code' ;начало сегмента кода<16> main        proc   ;начало процедуры main<17> assume cs:code,ds:data,ss:stk<18> mov   ax,data ;адрес сегмента данных в регистр ax<19> mov   ds,ax  ;ax в ds<20> mov   ah,9<21> mov   dx,offset message<22> int      21h<23> xor     ax,ax  ;очистить регистр ax<24> mov   ah,1h  ;1h в регистр ah<25> int      21h    ;генерация прерывания с номером 21h<26> mov   dl,al   ;содержимое регистра al в регистр dl<27> sub     dl,30h ;вычитание: (dl)=(dl)-30h<28> cmp    dl,9h  ;сравнить (dl) с 9h<29> jle       M1     ;перейти на метку M1 если dl<9h или dl=9h<30> sub     dl,7h  ;вычитание: (dl)=(dl)-7h<31> M1:                         ;определение метки M1<32> mov   cl,4h   ;пересылка 4h в регистр cl<33> shl      dl,cl   ;сдвиг содержимого dl на 4 разряда влево<34> int      21h    ;вызов прерывания с номером 21h<35> sub     al,30h ;вычитание: (dl)=(dl)-30h<36> cmp    al,9h   ;сравнить (al) с 9h          28<37> jle       M2     ;перейти на метку M2, если al<9h или al=9h<38> sub     al,7h   ;вычитание: (al)=(al)-7h<39> M2:                                        ;определение метки M2<40> add    dl,al   ;сложение: (dl)=(dl)+(al)<41> mov   ax,4c00h ;пересылка 4c00h в регистр ax<42> int      21h    ;вызов прерывания с номером 21h<43> main endp               ;конец процедуры main<44> code ends               ;конец сегмента кода<45> end main                 ;конец программы с точкой входа main

Структурно в ней явно прослеживаются повторяющиеся участки кода. Их можно оформить в виде макрокоманд и использовать эти повторяющиеся фрагменты в различных программах.

Дальнейшее наше обсуждение будет посвящено тому, как это сделать.

Определимся с терминологией.

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

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

Синтаксис макроопределения следующий:

имя_макрокоманды macro список_формальных_аргументов тело макроопределения          endm

Где должны располагаться макроопределения?

Есть три варианта:

  1. В начале исходного текста программы до сегмента кода и данных с тем, чтобы не ухудшать читабельность программы.
  2. Этот вариант следует применять в случаях, если определяемые вами макрокоманды актуальны только в пределах одной этой программы.
  3. В отдельном файле.
  4. Этот вариант подходит при работе над несколькими программами одной проблемной области. Чтобы сделать доступными эти макроопределения в конкретной программе, необходимо в начале исходного текста этой программы записать директиву include имя_файла, к примеру:
masmmodel smallinclude show.inc;в это место будет вставлен текст файла show.inc...
  1. В макробиблиотеке.
  2. Если у вас есть универсальные макрокоманды, которые используются практически во всех ваших программах, то их целесообразно записать в так называемую макробиблиотеку. Сделать актуальными макрокоманды из этой библиотеки можно с помощью все той же директивы include.

 

Недостаток двух последних способов в том, что в исходный текст программы включаются абсолютно все макроопределения.

Для исправления ситуации можно использовать директиву purge, в качестве операндов которой через запятую перечисляются имена макрокоманд, которые не должны включаться в текст программы.

К примеру,

...include iomac.incpurge _outstr,_exit...

В данном случае в исходный текст программы перед началом компиляции TASM вместо строки include iomac.inc вставит строки из файла iomac.inc. Но вставленный текст будет отличаться от оригинала тем, что в нем будут отсутствовать макроопределения _outstr и _exit.

А теперь вернемся к программе из листинга 1. Проанализируем ее текст, выявим повторяющиеся участки и составим для них макроопределения (листинг 2).

Листинг 2. Пример 1 создания и использования макрокоманд<1>;prg_3_1.asm с макроопределениями<2>init_ds        macro <3>;Макрос настройки ds на сегмент данных<4>    mov   ax,data<5>    mov   ds,ax<6>    endm <7>out_str        macro str<8>;Макрос вывода строки на экран.<9>;На входе — выводимая строка.<10>;На выходе - сообщение на экране.<11>  push   ax<12>  mov   ah,09h<13>  mov   dx,offset str<14>  int      21h<15>  pop    ax<16>  endm<17><18>clear_r      macro rg<19>;очистка регистра rg<20>  xor     rg,rg<21>  endm<22><23>get_char   macro<24>;ввод символа<25>;введенный символ в al<26>  mov   ah,1h<27>  int      21h<28>  endm<29><30>conv_16_2 macro<31>;макрос преобразования символа шестнадцатеричной цифры <32>;в ее двоичный эквивалент в al<33>  sub     dl,30h<34>  cmp    dl,9h<35>  jle       $+5<36>  sub     dl,7h<37>  endm<38><39>exit macro<40> ;макрос конца программы<41>  mov   ax,4c00h<42>  int      21h<43>  endm<44><45> data         segment para public 'data'<46> message   db      'Введите две шестнадцатеричные цифры (буквы A,B,C,D,E,F — прописные): $'<47> data         ends<48><49> stk segment stack<50>  db      256 dup('?')<51> stk ends<52><53> code        segment para public 'code'<54>  assume cs:code,ds:data,ss:stk<55> main        proc<56>  init_ds<57>  out_str message <58>  clear_r ax<59>  get_char<60>  mov   dl,al<61>  conv_16_2<62>  mov   cl,4h<63>  shl      dl,cl<64>  get_char<65>  conv_16_2<66>  add    dl,al<67>  xchg   dl,al   ;результат в al<68>  exit<69> main        endp<70> code        ends<71> end          main

В листинге 2 в строках 3–7, 9–18, 20–23, 25–30, 32–38, 40–44 описаны макроопределения. Их назначение приведено сразу после заголовка в теле каждого макроопределения.

Все эти макроопределения можно использовать и при написании других программ. Посмотрите на модернизированный исходный текст программы из листинга 3.1 в листинге 2 (строки 56–70). Если не обращать внимания на некоторые неясные моменты, то сам сегмент кода стал внешне более читабельным и даже можно сказать, что в нем появился какой то смысл.

Функционально макроопределения похожи на процедуры.

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

  • в отличие от процедуры, текст которой неизменен, макроопределение в процессе макрогенерации может меняться в соответствии с набором фактических параметров.
  • При этом коррекции могут подвергаться как операнды команд, так и сами команды. Процедуры в этом отношении объекты менее гибки;
  • при каждом вызове макрокоманды ее текст в виде макрорасширения вставляется в программу.
  • При вызове процедуры микропроцессор осуществляет передачу управления на начало процедуры, находящейся в некоторой области памяти в одном экземпляре. Код в этом случае получается более компактным, хотя быстродействие несколько снижается за счет необходимости осуществления переходов.

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

          имя_макрокоманды          список_фактических_аргументов

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

Обычно макрокоманда содержит некоторый список аргументовсписок_фактических_аргументов, которыми корректируется макроопределение.

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

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

Процесс такого замещения называется макрогенерацией, а результатом этого процесса является макрорасширение.

К примеру, рассмотрим самое короткое макроопределение в листинге 2 — clear_rg.

Как отмечено выше, результаты работы макроассемблера можно узнать, просмотрев файл листинга после трансляции. Покажем несколько его фрагментов, которые демонстрируют, как был описан текст макроопределения clear_rg (строки 24-27), как был осуществлен вызов макрокоманды clear_rg с фактическим параметром ax (строка 58) и как выглядит результат работы макрогенератора, сформировавшего команду ассемблера xor ax,ax (строка 75);

24                      clear_r macro rg25      ;очистка регистра rg26                      xor     rg,rg27                      endm...74                      clear_r ax75000E 33 C0  xor     ax,ax

Таким образом в итоге мы получили то, что и требовалось — команду очистки заданного регистра, в данном случае ax.

В другом месте программы вы можете выдать ту же макрокоманду, но уже с другим именем регистра.

Каждый фактический аргумент представляет собой строку символов, для формирования которой применяются следующие правила:

  • строка может состоять из:
    • последовательности символов без пробелов, точек, запятых, точек с запятой;
    • последовательности любых символов, заключенных в угловые скобки: <...>. В этой последовательности можно указывать как пробелы, так и точки, запятые, точки с запятыми.
    • Не забывайте о том, что угловые скобки < > — это тоже оператор ассемблера. Мы упоминали о них при обсуждении директивы equ;
  • для того чтобы указать, что некоторый символ внутри строки, представляющей фактический параметр, является собственно символом, а не чем-то иным, например некоторым разделителем или ограничивающей скобкой, применяется специальный оператор “!”.
  • Этот оператор ставится непосредственно перед описанным выше символом, и его действие эквивалентно заключению данного символа в угловые скобки (см. предыдущий пункт);
  • если требуется вычисление в строке некоторого константного выражения, то в начале этого выражения нужно поставить знак “%”:
  • % константное_выражение — значение константное_выражение вычисляется и подставляется в текстовом виде в соответствии с текущей системой счисления.

Теперь обсудим вопрос — как транслятор распознает формальные аргументы в теле макроопределения для их последующей замены на фактические аргументы?

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

Здесь нужно еще раз особо отметить список_формальных_аргументов в заголовке макроопределения.

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

          имя_формального_аргумента[:тип]

где тип может принимать значения:

  • REQ, которое говорит о том, что требуется обязательное явное задание фактического аргумента при вызове макрокоманды;
  • =<любая_строка> — если аргумент при вызове макрокоманды не задан, то в соответствующие места в макрорасширении будет вставлено значение по умолчанию, соответствующее значению любая_строка.
  • Будьте внимательны: символы, входящие в любая_строка, должны быть заключены в угловые скобки.

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

Этот прием часто используется для задания модифицируемых идентификаторов и кодов операций.

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

...def_table macro type=b,len=REQtabl_&type        d&type len dup (0)endm....datadef_tabl b,10def_tabl w,5

После того как вы подвергнете трансляции текст программы, содержащий эти строки, вы получите следующие макрорасширения:

tabl_b db      10 dup (0)tabl_w dw     10 dup (0)

Символ & можно применять и для распознавания формального аргумента в строке, заключенной в кавычки ' '. Например:

num_char          macro message;...;подсчитать количество (num) символов в строке          jmp    m1elem   db      'Строка &message содержит ';число символов в строке message в коде ASCIInum   db      2 dup (0)          db      ' символов',10,13,'$'          ;конец строки для вывода функцией 09hm1:;...;вывести elem на экран          endm

В связи с рассмотрением последнего фрагмента разберем ситуацию, когда тело макроопределения содержит метку или имя в директиве резервирования и инициализации данных. Если в программе некоторая макрокоманда вызывается несколько раз, то в процессе макрогенерации возникнет ситуация, когда в программе один идентификатор будет определен несколько раз, что, естественно, будет распознано транслятором как ошибка. Для выхода из подобной ситуации применяют директиву local, которая имеет следующий синтаксис:

local список_идентификаторов

Эту директиву необходимо размещать непосредственно за заголовком макроопределения.

Результатом работы этой директивы будет генерация в каждом экземпляре макрорасширения уникальных имен для всех идентификаторов, перечисленных в список_идентификаторов. Эти уникальные имена имеют вид ??xxxx, где хххх — шестнадцатеричное число.

Для первого идентификатора в первом экземпляре макрорасширения хххх= 0000, для второго — хххх= 0001 и т. д. Контроль за правильностью размещения и использования этих уникальных имен берет на себя ассемблер.

Для того чтобы вам окончательно все стало понятно, введем и подвергнем трансляции листинг 3. В нем, кроме некоторых ранее рассмотренных макрокоманд, содержится макрокоманда num_char. Ее назначение — подсчитывать количество символов в строке, адрес которой передается этой макрокоманде в качестве фактического параметра. Строка должна удовлетворять требованию, предъявляемому к строке, предназначенной для вывода на экран функцией 09h прерывания 21h, то есть заканчиваться символом $.

Другой момент, который нашел отражение в этой программе, — использование символа $ для распознавания формального аргумента в строке, заключенной в кавычки ' ' (см. последний фрагмент).

Листинг 3. Пример 2 создания и использования макрокоманд

;prg_13_2.asminit_ds macro;макрос настройки ds на сегмент данных          mov   ax,data          mov   ds,ax          xor     ax,ax          endmout_str macro str;макрос вывода строки на экран.;На входе — выводимая строка.;На выходе — сообщение на экране.          push   ax          mov   ah,09h          mov   dx,offset str          int      21h          pop    ax          endmexit    macro;макрос конца программы          mov   ax,4c00h          int      21h          endmnum_char          macro message          localm1,elem,num,err_mes,find,num_exit;макрос подсчета количества символов в строке.;Длина строки — не более 99 символов.;Вход: message — адрес строки символов, ограниченной '$';Выход: в al — количество символов в строке message и вывод сообщения          jmp    m1elem   db      'Строка &message содержит 'num   db      2 dup (0) ;число символов в строке message в коде ASCII          db      ' символов',10,13,'$'          ;конец строки для вывода функцией 09herr_mes db      'Строка &message не содержит символа конца строки',10,13,'$'m1:;сохраняем используемые в макросе регистры          push   es          push   cx          push   ax          push   di          push   ds          pop    es       ;настройка es на ds          mov   al,'$'   ;символ для поиска — `$`          cld                     ;сброс флага df          lea      di,message        ;загрузка в es:di смещения строки message          push   di       ;запомним di — адрес начала строки          mov   cx,99  ;для префикса repne — максимальная длина строки;поиск в строке (пока нужный символ и символ в строке не равны);выход — при первом совпавшемrepne  scasb          je        find    ;если символ найден — переход на обработку;вывод сообщения о том, что символ не найден          push   ds;подставляем cs вместо ds для функции 09h (int21h)          push   cs          pop    ds          out_str err_mes          pop    ds          jmp    num_exit ;выход из макросаfind:   ;совпали;считаем количество символов в строке:          pop    ax       ;восстановим адрес начала строки          sub     di,ax  ;(di)=(di)-(ax)          xchg   di,ax  ;(di) <-> (ax)          sub     al,3     ;корректировка на служебные символы — 10, 13, '$'          aam                   ;в al две упакованные BCD-цифры результата подсчета          or       ax,3030h ;преобразование результата в код ASCII          mov   cs:num,ah          mov   cs:num+1,al;вывести elem на экран          push   ds;подставляем cs вместо ds для функции 09h (int21h)          push   cs          pop    ds          out_str elem          pop    dsnum_exit:          push   di          push   ax          push   cx          push   es          endm data   segment para public 'data'msg_1 db      'Строка_1 для испытания',10,13,'$'msg_2 db      'Строка_2 для второго испытания',10,13,'$'data ends stk      segment stack          db      256 dup('?')stk      ends code   segment para public 'code'          assume cs:code,ds:data,ss:stkmain   proc          init_ds          out_str msg_1          num_char          msg_1          out_str msg_2          num_char          msg_2          exitmain   endpcode   endsend    main

В теле макроопределения можно размещать комментарии и делать это особым образом.

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

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

mes    macro messsage...                       ;этот комментарий будет включен в текст листинга...                       ;;этот комментарий не будет включен в текст листинга          endm

Макродирективы

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

  • директивы повторения WHILE, REPT, IRP и IRPC.
  • Директивы этой группы предназначены для создания макросов, содержащих несколько идущих подряд одинаковых последовательностей строк. При этом возможна частичная модификация этих строк.
  • директивы управления процессом генерации макрорасширения EXITM и GOTO.
  • Они предназначены для управления процессом формирования макрорасширения из набора строк соответствующего макроопределения. С помощью этих директив можно как исключать отдельные строки из макрорасширения, так и вовсе прекращать процесс генерации. Директивы EXITM и GOTO обычно используются вместе с условными директивами компиляции, поэтому они будут рассмотрены вместе с ними.

Директивы WHILE и REPT

Директивы WHILE и REPT применяют для повторения определенное количество раз некоторой последовательности строк.

Эти директивы имеют следующий синтаксис:

WHILE константное_выражениепоследовательность_строкENDM

 

REPT константное_выражениепоследовательность строкENDM

Обратите внимание, что последовательность повторяемых строк в обеих директивах ограничена директивой ENDM.

При использовании директивы WHILE макрогенератор транслятора будет повторять последовательность_строк до тех пор, пока значение константное_выражение не станет равно нулю. Это значение вычисляется каждый раз перед очередной итерацией цикла повторения (то есть значение константное_выражение должно подвергаться изменению внутри последовательность_строк в процессе макрогенерации).

Директива REPT, подобно директиве WHILE, повторяет последовательность_строк столько раз, сколько это определено значением константное_выражение. Отличие этой директивы от WHILE состоит в том, что она автоматически уменьшает на единицу значение константное_выражение после каждой итерации.

В качестве примера рассмотрим листинг 4, в котором демонстрируется применение директив WHILE и REPT для резервирования области памяти в сегменте данных. Имя идентификатора и длина области задаются в качестве параметров для соответствующих макросов def_sto_1 и def_sto_2.

Листинг 4. Использование директив повторения;prg_13_3.asmdef_sto_1         macro id_table,ln:=<5>;макрос резервирования памяти длиной len.;Используется WHILEid_table label   bytelen=ln          while  len          db      0          len=len-1          endmendmdef_sto_2         macro id_table,len;макрос резервирования памяти длиной lenid_table label   byte          rept    len          db      0          endmendm data   segment para public 'data'          def_sto_1         tab_1,10          def_sto_2         tab_2,10data   ends;сегменты команд и стека в этой программе необязательныend

Заметьте, что счетчик повторений в директиве REPT уменьшается автоматически после каждой итерации цикла. Проанализируйте результат трансляции листинга 13.3.

Таким образом, директивы REPT и WHILE удобно применять для “размножения” в тексте программы последовательности одинаковых строк без внесения в эти строки каких-либо изменений.

Следующие две директивы, IRP и IRPC, делают этот процесс более гибким, позволяя модифицировать на каждой итерации некоторые элементы в последовательность_строк.

Директива IRP

Директива IRP имеет следующий синтаксис:

IRP формальный_аргумент,<строка_символов_1,...,строка_символов_N>          последовательность_строкENDM

Действие данной директивы заключается в том, что она повторяет последовательность_строк N раз, то есть столько раз, сколько строк_символов заключено в угловые скобки во втором операнде директивы IRP. Но это еще не все.

Повторение последовательности_строк сопровождается заменой в ней формального_аргумента строкой символов из второго операнда.

Так, при первой генерации последовательности_строк формальный_аргумент в них заменяется на строка_символов_1.

Если есть строка_символов_2, то это приводит к генерации второй копии последовательности_строк, в которой формальный_аргумент заменяется на строка_символов_2. Эти действия продолжаются до строка_символов_N включительно.

К примеру, рассмотрим результат определения в программе следующей конструкции:

          irp      ini,<1,2,3,4,5>          db      ini          endm

Макрогенератором будет сгенерировано следующее макрорасширение:

          db      1          db      2          db      3          db      4          db      5

Директива IRPC

Директива IRPC имеет следующий синтаксис:

IRPC          формальный_аргумент,строка_символов          последовательность строкENDM

Действие данной директивы подобно IRP, но отличается тем, что она на каждой очередной итерации заменяет формальный_аргумент очередным символом из строка_символов.

Понятно, что количество повторений последовательность_строк будет определяться количеством символов в строка_символов.

К примеру:

          irpc    rg,          push   rg&x          endm

В процессе макрогенерации эта директива развернется в следующую последовательность строк:


Дата добавления: 2019-09-13; просмотров: 282; Мы поможем в написании вашей работы!

Поделиться с друзьями:






Мы поможем в написании ваших работ!