Макросы

Последнее обновление: 28.12.2022

Макрос определяется с помощью директивы .MACRO. Она устанавливает имя макроса и список его параметров. Макрос должен завершаться директивой .ENDM:

.MACRO имя_макроса параметр1, параметр2, ... параметрN
// тело макроса 
.ENDM

Например, пусть у нас будет определен файл copy.s со следующим кодом:

// Макрос copy, который копирует символы из одной строки в другую
// Параметры макроса
// X0 - адрес входящей строки
// X1 - адрес исходящей строки
//
// Результат 
// X0 - длина строки
.MACRO copy inputstr, outputstr
    LDR X0, =\inputstr
    LDR X1, =\outputstr
    MOV X4, X1          // сохраняем адрес начала строки, чтобы потом вычислить ее длину
// в цикле получаем все байты, пока не дойдем до нулевого байта
1: 
    LDRB W5, [X0], #1        // загружаем из X0 один байт - один символ в W5 и увеличиваем адрес в X0 на 1 байт
    CMP W5, #0              // сравниваем с нулевым байтом
    B.EQ 2f            // если нулевой байт, переход к метке 2 
    STRB W5, [X1], #1      // если символы равны, заменяем байт по адресу X1 и увеличиваем адрес в X1 на 1 байт
    B 1b                  // перед обратно к метке 1
2:    
    SUB X0, X1, X4      // помещаем в регистр X0 длину строки -  X0 = X1 - X4
.ENDM

В данном случае макрос называется copy, и он принимает два параметра - inputstr (входная строка) и outputstr (строка, в которую надо скопировать символы):

.MACRO copy inputstr, outputstr

Внутри макроса мы можем обращаться к данным параметра, указав перед именем параметра слеш \:

LDR X0, =\inputstr
LDR X1, =\outputstr

То есть через оба параметра мы получаем соответствующие строки и загружаем их адреса в регистры X0 и X1.

В самом макросе производятся действия, которые уже рассматривались в предыдущих темах - проходим по всем символам из inputstr, и пока не встретим в строке нулевой байт, копируем каждый байт в строку outputstr.

Но обратите внимание на метки. Здесь используются НЕ текстовые метки, а именно числовые. Делов в том, что если макрос применяется в программе более одного раза, то текстовые метки будут повторяться, соответственно при компиляции мы столкнемся с ошибкой. Числовые же метки типа 1, 2 и т.д. позволяют уйти от подобного ограничения.

Также обратите внимание на суффиксы у меток при переходах

B.EQ 2f
B 1b

Суффикс f в 2f означает, что следующая метка 2 расположена дальше по тексту программы (то есть впереди - forward). b в 1b означает, что следующая метка 1 расположена в обратном направлении, позади (backward).

Стоит отметить, что при определении макросов не надо использовать инструкцию RET, как в случае с функцией.

Теперь используем этот макрос. Для этого определим файл main.s со следующим кодом:

.include "copy.s"    // вставляем в это место содержимое файла copy.s

.global _start 
_start: 
// копируем строку input1 в output
    copy input1, output
    MOV X2, X0          // в регистр X2 передаем результат из copy - длину строки из регистра X0
    MOV X0, #1          // 1 = StdOut - стандартный поток вывода
    LDR X1, =output     // загружаем адрес выводимой строки
    MOV X8, #64         // функция Linux для вывода в поток
    SVC 0               // вызываем функцию Linux

// копируем строку input2 в output
    copy input2, output
    MOV X2, X0          // в регистр X2 передаем результат из copy - длину строки из регистра X0
    MOV X0, #1          // 1 = StdOut - стандартный поток вывода
    LDR X1, =output     // загружаем адрес выводимой строки
    MOV X8, #64         // функция Linux для вывода в поток
    SVC 0               // вызываем функцию Linux

// выход из программы
    MOV X0, 0         // код возврата - 0
    MOV X8, #93       // устанавливаем функцию Linux для выхода из программы
    SVC 0             // Вызываем функцию Linux

.data
    input1: .asciz "Hello METANIT.COM!\n"
    input2: .asciz "Hello World!\n"
    output: .fill 256, 1, 0

Вначале файла вставляем код макроса с помощью директивы

.include "copy.s"

Далее вызываем макрос, передавая ему соответствующие строки для его параметров:

copy input1, output

По сравнению с вызовом функций здесь не надо использовать инструкцию BL.

После копирования первой строки input1 снова вызываем макрос, передавая ему для копирования вторую строку input2. Соответственно на консоль будет выведено

Hello METANIT.COM!
Hello World!

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

.include "copy.s"    // вставляем в это место содержимое файла copy.s

.MACRO print str
    MOV X2, X0          // в регистр X2 передаем результат макроса copy - длину строки из регистра X0
    MOV X0, #1          // 1 = StdOut - стандартный поток вывода
    LDR X1, =\str       // загружаем адрес выводимой строки
    MOV X8, #64         // функция Linux для вывода в поток
    SVC 0               // вызываем функцию Linux
.ENDM

.global _start 
_start: 
// копируем строку input1 в output
    copy input1, output
    print output

// копируем строку input2 в output
    copy input2, output
    print output

// выход из программы
    MOV X0, 0         // код возврата - 0
    MOV X8, #93       // устанавливаем функцию Linux для выхода из программы
    SVC 0             // Вызываем функцию Linux

.data
    input1: .asciz "Hello METANIT.COM!\n"
    input2: .asciz "Hello World\n"
    output: .fill 256, 1, 0

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

Макросы или функции?

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

Помощь сайту
Юмани:
410011174743222
Перевод на карту
Номер карты:
4048415020898850