Препроцессор. Многострочные макросы.

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

Многострочные макросы в большинстве своем похожи на макросы в 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-синтаксис.

Основы смешанного программирования

Наши рекомендации