Отличительной особенность FASM является очень гибкая и мощная поддержка макросов. В этой статье мы рассмотрим лишь основы создания макросов, так как эта тема довольно обширна и рассказать всё в одной статье не получится.
Что же такое макросы? Макросы — это шаблоны для генерации кода. Один раз создав макрос, мы можем использовать его во многих местах в коде программы. Макросы делают процесс программирования на ассемблере более приятным и простым, а код программы получается понятнее. Макросы позволяют расширять синтаксис ассемблера и даже добавлять собственные «команды», которых нет в процессоре.
Обработкой макросов занимается препроцессор FASM. Преобразование исходного кода в исполняемый код FASM выполняет в два этапа. Первый этап — препроцессирование, а второй — собственно ассемблирование или компиляция. На первом этапе происходит вычисление всех числовых выражений, вместо констант и названий меток подставляются их фактические значения, вместо макросов подставляется сгенерированный код. На втором этапе все данные и машинные команды преобразуются в соответствующие байты, и в результате получается исполняемый файл требуемого формата.
Синтаксис создания макроса
Для создания макроса используется директива macro. Эта директива имеет следующий синтаксис:
macro <название_макроса> [<список_параметров>]
{
<тело_макроса>
}
После директивы пишется название макроса, а также может быть указан список параметров (операндов). Параметры в списке перечисляются через запятую. Внутри фигурных скобок записывается тело макроса. Кстати, для коротких макросов можно писать всё это в одной строке:
macro <название_макроса> [<список_параметров>] { <тело_макроса> }
Тело макроса — это код, который подставляется в то место, где макрос будет вызван. Создание макроса является по сути лишь его объявлением, в этом месте программы никакого кода сгенерировано не будет! Поэтому объявления макросов обычно размещают в самом начале программы или в отдельном файле.
Примеры макросов
В качестве примера рассмотрим простой макрос без параметров, который предназначен для завершения программы:
macro exit_app
{
mov ax,4C00h
int 21h
}
После того, как макрос объявлен, в нужном месте программы достаточно написать exit_app. Туда препроцессор FASM автоматически подставит 2 команды, записанные в теле макроса. Создадим ещё один полезный макрос, предназначенный для такой часто используемой операции, как вывод строки:
macro print_str str
{
mov ah,9
mov dx,str
int 21h
}
У этого макроса есть один параметр — адрес строки. При генерации кода вместо str будет подставлен тот параметр, который указан при вызове макроса. Обратите внимание, что код будет генерироваться в месте каждого вызова макроса! В этом главное отличие макроса от процедуры. Код процедуры содержится в программе только в одном экземпляре, а вызывается она с помощью передачи управления командой CALL [1].
Теперь добавим эти макросы в программу «hello, world!» из части 6 учебного курса [2]:
; Макрос выхода из программы
macro exit_app
{
mov ax,4C00h ;Здесь только объявление макроса, код не генерируется
int 21h
}
; Макрос вывода строки
macro print_str str
{
mov ah,9 ;Здесь тоже код не генерируется
mov dx,str
int 21h
}
;-------------------------------------------------------------------------------
use16 ;Генерировать 16-битный код
org 100h ;Программа начинается с адреса 100h
print_str hello ;Вывод строки
exit_app ;Выход из программы
;-------------------------------------------------------------------------------
hello db 'Hello, macro world!$'
В результате основная программа состоит всего из двух строчек, и это уже не похоже на ассемблер, это — макроассемблер FASM 🙂
Расширение системы команд
Макросы можно использовать для расширения системы команд. Например, часто в программе приходится обнулять регистры. Создадим специальный макрос для этой цели:
; Макрос - команда обнуления регистра
macro clr reg { xor reg,reg }
Теперь обнулять регистры в программе можно так:
clr ax ;AX=0
clr si ;SI=0
clr bl ;BL=0
Макросы с переменным количеством параметров
Возможно также создавать макросы с переменным количеством параметров. В этом случае имя параметра записывается в квадратных скобках. Для генерации кода макрос вызывается столько раз, сколько параметров ему было передано. Например, можно написать специальные макросы для улучшения команд PUSH [3] и POP [4]:
; Макрос - улучшенная команда push
macro push [arg] { push arg }
; Макрос - улучшенная команда pop
macro pop [arg] { pop arg }
Несмотря на то, что название макроса совпадает с именем команды, в теле макроса это имя считается именем команды (иначе получилась бы бесконечная рекурсия 🙂 ). Данные макросы позволяют помещать в стек и извлекать из стека сразу несколько операндов, что упрощает код программы. Пример использования:
push ax,word[si],5
pop dx,cx,ax
В результате препроцессором будет сгенерирован следующий код:
push ax
push word[si]
push 5
pop dx
pop cx
pop ax
Директива include
Возможно, вам захочется написать собственный набор макросов и использовать их в своих программах. В этом случае удобно поместить макросы в отдельный файл и воспользоваться директивой включения файла include. Синтаксис директивы include очень прост:
include 'путь/к/файлу'
Путь к файлу указывается в одинарных кавычках и может быть относительным (по отношению к компилируемому файлу) или полным (начиная от буквы диска или корневого каталога системы). Если включаемый файл находится в той же папке, то достаточно указать только имя файла. Расширение файла может быть любым, но обычно используют «inc» или «asm».
Препроцессор FASM читает указанный файл и подставляет код из него вместо директивы include. В отдельный файл можно также вынести часто используемые процедуры, отдельные функциональные блоки программы или даже объявления данных.
Если записать макросы в отдельный файл ‘mymacro.inc’, то программа «hello, world!» станет ещё короче:
include 'mymacro.inc'
use16 ;Генерировать 16-битный код
org 100h ;Программа начинается с адреса 100h
print_str hello ;Вывод строки
exit_app ;Выход из программы
;-------------------------------------------------------------------------------
hello db 'Hello, macro world!$'
Упражнение
Напишите макрос для определения максимального значения. У макроса должно быть 3 операнда: второй и третий сравниваются между собой, больший из них помещается на место первого. Результаты можете писать в комментариях.