- FasmWorld - https://fasmworld.ru -

Учебный курс. Часть 28. Основы создания макросов

Отличительной особенность 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 операнда: второй и третий сравниваются между собой, больший из них помещается на место первого. Результаты можете писать в комментариях.

Следующая часть » [5]