Макрокоманды в ассемблере

Язык ассемблера / Макрокоманды в ассемблере

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

  • макроассемблер;
  • механизм процедур;
  • механизм прерываний.

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

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

ИмяМакрокоманды macro СписокФормальныхАргументов
ТелоМакроопределения
endm

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

ИмяМакрокоманды СписокФактическихАргументов

Результатом применения данной синтаксической конструкции в исходном тексте программы будет ее замещение строками из конструкции ТелоМакроопределения. Но это не простая замена. Обычно макрокоманда содержит некоторый список аргументов — СписокФактическихАргументов, которыми корректируется макроопределение. Места в теле макроопределения, которые будут замещаться фактическими аргументами из макрокоманды, обозначаются с помощью так называемых формальных аргументов. Таким образом, в результате применения макрокоманды в программе формальные аргументы в макроопределении замещаются соответствующими фактическими аргументами; в этом и заключается учет контекста. Процесс такого замещения называется макрогенерацией, а результатом этого процесса является макрорасширение.

Макрокоманды в ассемблере схожи с директивой #define в языке Си.

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

  • в начале исходного текста программы, до кода и данных с тем, чтобы не ухудшать читаемость программы. Этот вариант следует применять в случаях, если определяемые макрокоманды актуальны только в пределах одной этой программы;
  • в отдельном файле. Этот вариант подходит при работе над несколькими программами одной проблемной области. Чтобы сделать доступными эти макроопределения в конкретной программе, необходимо в начале исходного текста этой программы записать директиву include ИмяФайла, например:
    .586
    .model flat, stdcall
    include show.inc ;сюда вставляется текст файла show.inc
  • в макробиблиотеке. Универсальные макрокоманды, которые используются практически во всех программах целесообразно записать в так называемую макробиблиотеку. Сделать актуальными макрокоманды из этой библиотеки можно также с помощью директивы include. Недостаток этого и предыдущего способов в том, что в исходный текст программы включаются абсолютно все макроопределения. Для исправления ситуации можно использовать директиву purge, в качестве операндов которой через запятую перечисляются имена макрокоманд, которые не должны включаться в текст программы. К примеру:

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

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

  • Строка может состоять:
    - из последовательности символов без пробелов, точек, запятых, точек с запятой;
    - из последовательности любых символов, заключенных в угловые скобки: <...>. В этой последовательности можно указывать как пробелы, так и точки, запятые, точки с запятыми.
  • Для того чтобы указать, что некоторый символ внутри строки, представляющей фактический параметр, является собственно символом, а не чем-то иным, например, некоторым разделителем или ограничивающей скобкой, применяется специальный оператор !. Этот оператор ставится непосредственно перед описанным выше символом, и его действие эквивалентно заключению данного символа в угловые скобки.
  • Если требуется вычисление в строке некоторого константного выражения, то в начале этого выражения нужно поставить знак %:
    %КонстантноеВыражение

    Значение КонстантноеВыражение вычисляется и подставляется в текстовом виде в соответствии с текущей системой счисления.

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

ИмяФормальногоАргумента: Тип

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

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

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

.686p
.model flat, stdcall
def_table macro t:REQ, len:=<1>
tabl_&t d&t len dup(5)
endm
.data
def_table d, 10
def_table b
.code
main proc
mov al, [tabl_b]
mov ah, [tabl_b+1]
mov ebx, [tabl_d]
ret
main endp
end main

После трансляции текста программы, содержащего строки сегмента данных, получится

def_table d, 10         ;tabl_d dd 10 dup(5)
def_table b             ;tabl_b db 1 dup(5)

Заметим, что строка программы

mov ah, [tabl_b+1]

поместит в ah число отличное от 5, поскольку память для tabl_b распределена только под 1 элемент массива длиной 1 байт со значением 5.

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

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

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

Эту директиву необходимо задавать непосредственно за заголовком макроопределения. Результатом работы этой директивы будет генерация в каждом экземпляре макрорасширения уникальных имен для всех идентификаторов, перечисленных в CписокИдентификаторов. Эти уникальные имена имеют вид ??хххх, где хххх — шестнадцатеричное число. Для первого идентификатора в первом экземпляре макрорасширения хххх=0000, для второго — хххх=0001 и т. д. Контроль за правильностью размещения и использования этих уникальных имен берет на себя транслятор.

Для примера использования макроопределений рассмотрим программу вывода имени в диалоговое окно.

.686P
.MODEL FLAT, STDCALL
PrintName macro Name
local STR1, STR2, METKA
jmp METKA
STR1    DB "Программа",0
STR2 DB "Меня зовут: &Name ",0
METKA:
PUSH     0
PUSH     OFFSET STR1
PUSH     OFFSET STR2
PUSH     0
CALL     MessageBoxA@16
endm
Init macro
EXTERN MessageBoxA@16:NEAR
endm
Init
.CODE
START:
PrintName <Лена>
PrintName <Таня>
RET
END START

Результат выполнения

macro

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

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

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

Назад


Назад: Язык ассемблера

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *