Препроцессор. Многострочные макросы.
Препроцессор — это компьютерная программа, принимающая данные на входе и выдающая данные, предназначенные для входа другой программы (например,компилятора). О данных на выходе препроцессора говорят, что они находятся в препроцессированной форме, пригодной для обработки последующими программами (компилятор). Результат и вид обработки зависят от вида препроцессора; так, некоторые препроцессоры могут только выполнить простую текстовую подстановку, другие способны по возможностям сравниться с языками программирования.
Многострочные макросы в большинстве своем похожи на макросы в MASM и TASM: определение многострочного макроса в NASM выглядит похоже.
%macro prologue 1
push ebp
mov ebp,esp
sub esp,%1
%endmacro
Здесь определяется как макрос С-подобная функция prologue: теперь вы можете вызвать макрос следующим образом:
myfunc: prologue 12
что будет развернуто в три строки кода
myfunc: push ebp
mov ebp,esp
sub esp,12
Число 1 после имени макроса в строке %macro определяет число параметров, которые ожидает получить макрос prologue. Конструкция %1 внутри тела макроса ссылается на первый передаваемый параметр. В случае макросов, принимающих более одного параметра, ссылка на них осуществляется как %2,%3 и т.д.
Многострочные макросы, как и однострочные, чувствительны к регистру символов, за исключением случая, когда вы примените альтернативную директиву %imacro.
Если вам требуется передать в многострочный макрос запятую в качестве составной части параметра, вы можете сделать это, заключив параметр в фигурные скобки. Например:
%macro silly 2
%2: db %1
%endmacro
silly 'a', letter_a ; letter_a: db 'a'
silly 'ab', string_ab ; string_ab: db 'ab'
silly {13,10}, crlf ; crlf: db 13,10
Команды работы со строками. Примеры.
Команды обработки строковых примитивов
В системе команд процессоров Intel предусмотрено пять групп команд для обработки массивов байтов, слов и двойных слов (таблица). Для адресации памяти в командах, приведенных в таблице, используется регистр ESI, EDI или сразу оба этих регистра.
Особенность этих команд состоит в том, их операнды расположены в памяти. При обработке строковых примитивов эти команды могут автоматически повторяться, что делает их применение особенно удобным для работы с длинными строками и массивами.
Команда | Название | Описание |
MOVSB,MOVSW, MOVSD | Move String data, или Переместить строку данных | Копирует целочисленные данные из одного участка памяти в другой |
CMPSB, CMPSW, CMPSD | Compare strings, или Сравнить строки | Сравнивает значение двух участков памяти произвольной длины |
SCASB,SCASW, SCASD | Scan string, или Сканировать строку | Сравнивает целочисленное значение с содержимым участка памяти |
STOSB, STOSW, STOSD | Store String data, или Сохранить строковые данные | Записывает целочисленное значение в участок памяти произвольной длины |
LODSB, LODSW, LODSD | Load accumulator from string, или Загрузить строковые данные | Загружает целочисленное значение из памяти в аккумулятор (регистр AL, АХ или ЕАХ) |
Команды MOVSB, MOVSW и MOVSD
Пример: копирование массива двойных слов. Предположим, что нам нужно скопировать массив, состоящий из двадцати двойных слов из переменной source в переменную target. После копирования данных, в регистрах ESI и EDI будут находиться значения, соответствующие адресам 21-го элемента массивов source и target (если бы они существовали):
segment .data
source times 10 DD 0xFFFFFFFF
msglen equ $-source
target times 10 DD 0
segment .text
cld ;Сбросим флаг DF и установим прямое направление
mov ecx,LENGTHOF source ;Зададим значение счетчика
mov esi, source ;Зададим адрес источника данных
mov edi, target ; Зададим адрес получателя данных
rep movsd ; Копируем 20 двойных слов
Команды CMPSB, CMPSW и CMPSD
Эти команды позволяют сравнить данные из одного участка памяти, адрес которого указан в регистре ESI, с другим участком памяти, адрес которого указан в регистре EDI.
С командами CMPSB, CMPSW и CMPSD может использоваться префикс повторения.
Пример, Предположим, что мы хотим сравнить значения двух двойных слов, расположенных в памяти, с помощью команды CMPSD. В приведенном ниже фрагменте кода можно заметить, что исходный операнд (переменная source) меньше операнда получателя данных (переменная target). Поэтому при выполнении команды JA не произойдет переход программы на метку L1, а будет выполнена следующая за ней команда JMР:
segment .data
source DD 1234h
target DD 5678h
segment .data
mov esi, source
mov edi, target
cmpsd ; Сравним двойные слова
ja LI ; Перейдем, если source > target
jmp L2 ; Перейдем, если source <= target
Если же мы хотим сравнить между собой несколько двойных слов, нам нужно сбросить флаг направления DF, загрузить в регистр ECX счетчик повторения и поместить перед командой CMPSD префикс повторения REPE:
mov esi, source
mov edi, target
cld ;Направление сравнения восходящее
mov ecx, [LENGTHsource] ; Загрузим счетчик повторения
repe cmpsd ;Повторим сравнение, пока операнды равны
Команды SCASB, SCASW и SCASD
Эти команды сравнивают значение, находящееся в регистрах AL/AX/EAX с байтом, словом или двойным словом, адресуемым через регистр EDI. Данная группа команд обычно используется при поиске какого-либо значения в длинной строке или массиве. Если перед командой SCAS поместить префикс REPE (или REP), строка или массив будет сканироваться до тех пор, пока значение в регистре ECX не станет равным нулю, либо пока не будет найдено значение в строке или массиве, отличное от того, что находится в регистре AL/AX/EAX (т.е. пока не будет сброшен флаг нуля ZF). При использовании префикса REPNE, строка или массив будет сканироваться до тех пор, пока значение в регистре ECX не станет равным нулю, либо пока не будет найдено значение в строке или массиве, совпадающее с тем, что находится в регистре AL/AX/EAX (т.е. пока не будет установлен флаг нуля ZF).
Пример. Поиск символов в строке, В приведенном ниже фрагменте кода выполняется поиск символа F в строке alpha. При нахождении данного символа, в регистре EDI будет содержаться его адрес плюс единица. Если же искомого символа нет в исходной строке, то работа программы завершается в результате перехода по команде JNZ:
SECTION.data
alpha db "ABCDEFGH",0
msglen equ $-alpha
SECTION .text
_main:
mov edi, alpha ;Загрузим в EDI адрес строки alpha
mov al,'F' ; Загрузим в AL ASCII-код символа "F"
mov ecx, msglen ; Загрузим в ECX длину строки alpha
cld ;Направление сравнения восходящее
repne scasb ; Сканируем строку пока не найдем символ "F"
jnz quit ; Если не нашли, завершим работу
dec edi ;Здесь символ найден, в EDI его адрес
Команды STOSB, STOSW и STOSD
Эта группа команд позволяет сохранить содержимое регистра AL/AX/EAX в памяти, адресуемой через регистр EDI. При выполнении команды STOS содержимое регистра EDI изменяется в соответствии со значением флага направления DF и типом используемого в команде операнда. При использовании совместно с префиксом REP, с помощью команды STOS можно записать одно и то же значение во все элементы массива или строки. Например, в приведенном ниже фрагменте кода выполняется инициализация строки stringl значением 0FFh:
SECTION .data
count dd 2
stringl times 11 db 0
SECTION .text
_main:
mov al, 0x61 ; Записываемое значение
mov edi, stringl ; Загрузим в EDI адрес строки
mov ecx, [count] ; Загрузим в ECX длину строки
cld ; Направление сравнения восходящее
rep stosb ;Заполним строку содержимым AL
Команды LODSB, LODSW и LODSD
Эта группа команд позволяет загрузить в регистр AL/AX/EAX содержимое байта, слова или двойного слова памяти, адресуемого через регистр ESI. Префикс REP практически никогда не используется с командой LODS. Таким образом, эта команда используется для загрузки одного значения в аккумулятор. Например, команду LODSB можно использовать вместо двух приведенных ниже команд (если флаг направления DF не установлен):
mov al,[esi] ;Загрузим байт в AL
inc esi ; Адрес следующего байта
Умножение элементов массива, В приведенной ниже программе каждый элемент массива двойных слов array умножается на постоянное значение. Для загрузки в регистр EAX текущего элемента массива используется команда LODSD, а для сохранения — STOSD.
extern _printf
GLOBAL _main
SECTION .data
array dd 1,2,3,4,5,6,7,8,9,10
multiplier db 2
fp db "%d, ", 0
SECTION .text
_main:
cld ; Направление -- восходящее
mov esi, array ; Загрузим адрес массива в регистры ESI и EDI
mov edi, esi
mov ecx, 10 ; Загрузим длину массива
L1:
lodsd ;Загрузим текущий элемент массива
mul byte [multiplier] ;Умножим его на константу
stosd ; Запишем EAX в текущий элемент
loop L1 ;массива (его адрес в [EDI])
mov esi, array
mov ecx, 10
cyc:
push ecx
lodsd
push eax
push fp
call _printf
add esp, 8
pop ecx
loop cyc
mov ax, 0
ret
20. Основные конструкции языка. AT&T-синтаксис.
Основы смешанного программирования