Форум программистов, компьютерный форум, киберфорум
Assembler, MASM, TASM
Войти
Регистрация
Восстановить пароль
Блоги Сообщество Поиск Заказать работу  
 
 
Рейтинг 4.63/2256: Рейтинг темы: голосов - 2256, средняя оценка - 4.63
Ушел с форума
Автор FAQ
 Аватар для Mikl___
16373 / 7685 / 1080
Регистрация: 11.11.2010
Сообщений: 13,759
18.12.2013, 14:19  [ТС]
ГЛАВА 21
МАКРОЯЗЫК
(часть 2/3)


Примеры использования макросов
Одним из недостатков программирования на языке ассемблера — его чрезвычайная низкоуровневость операций. Например, язык ассемблера может сложить два числа, а вот для сложения трех чисел необходимо писать специальную программу. Такое сведение к операциям низкого уровня приходится делать для любого алгоритма каким бы сложным он не был. В определенной мере этот недостаток устраняется с помощью макросов. Для этого надо в виде макросов описать более крупные операции, а затем составлять программу с использованием этих макросов.
Пусть в нашей программе переход «если меньше, то». Эта операция реализуется тремя командами:
Assembler
1
2
3
4
5
IF_LESS MACRO X,Y,L
MOV AX,X
CMP AX,Y
JL L
ENDM
Чтобы каждый раз не выписывать эту группу команд заново, имеет смысл описать ее как макрос, а затем пользоваться этим макросом. Имея макрос IF_LESS, можно следующим образом описать вычисление минимума трех чисел:
До макрогенерации После макрогенерации
MOV DX,A
IF_LESS A,B,M1
MOV DX,B
M1: IF_LESS DX,C,M2
MOV DX,C
M2:
MOV DX,A
MOV AX,A
CMP AX,B
JL M1
MOV DX,B
M1: MOV AX,DX
CMP AX,C
JL M2
MOV DX,C
M2:
Использование макросов сокращает размеры исходного текста программы и позволяет составлять программу в терминах более крупных операций. Если в виде макросов описать все часто используемые операции, то можно построить новый язык программирования, создавать программы на котором существенно легче, чем на чистом языке ассемблера. История гласит, что язык Си был создан потому, что его авторам Бриану Керниган (Brian Kernighan) и Дэннису Ритчи (Dennis Ritchie) надоело писать программы на языке ассемблера. История умалчивает о том, что они отнюдь не презирали язык ассемблера как таковой. Наверняка они отдавали должное возможности программировать на самой грани между программными и аппаратными средствами; но есть основания подозревать, что кому-то из них больше по душе были макросы.
Предположим, что имеется процедура вычисляющая наибольший общий делитель (NOD). Параметр X передается через регистр AX, параметр Y — через регистр BX, результат Z возвращается через AX. Вычисляем CX=NOD(A,B)+NOD(C,D). На рисунке 58 находится соответствующий фрагмент программы:
Assembler
1
2
3
4
5
6
7
8
MOV AX,A
MOV BX,B
CALL NOD
MOV CX,AX
MOV CX,AX
MOV BX,D
CALL NOD
ADD CX,AX
При каждом обращении к процедуре NOD выписывается почти одна и та же группа команд. Опишем эту группу как макрос, а затем его используем. Описание этого макроса:
Assembler
1
2
3
4
5
CALL_NOD MACRO X,Y
MOV AX,X
MOV BX,Y
CALL NOD
ENDM
Нужный нам фрагмент программы выглядит более наглядно и короче:
Assembler
1
2
3
4
CALL_NOD
MOV CX,AX
CALL_NOD
ADD CX,AX
После макроподстановок получится тот же текст программы, что и прежде, но построением такого текста будет заниматься макрогенератор, а не программист.
При входе в процедуру приходится сохранять в стеке содержимое регистров, для чего многократно записывается несколько команд PUSH, а при выходе из процедуры несколько команд POP. Если в программе много процедур — стоит облегчить себе жизнь, записав эти команды в виде макросов. У этих макросов может быть любое количество фактических параметров (разное количество названий регистров), так как в языке ассемблера можно определять макросы с фиксированным числом формальных параметров, поэтому макрос описывается с одним формальным параметром, но при обращении к нему в макрокоманде указывают через запятую нужное количество фактических параметров и заключают весь список в угловые скобки, в результате получается синтаксически один параметр. В теле макроса от этого списка отделяют по одному настоящему параметру и что-то с ним делают. Макрос должен поставлять в текст программы (в макрорасширение) несколько однотипных команд PUSH R. Здесь воспользуемся блоком повторения:
Assembler
1
2
3
4
5
SAVE MACRO REGS
IRP R,<REGS>
PUSH R
ENDM
ENDM
По макрокоманде SAVE <AX,SI,BP> в теле макроса формальный параметр будет заменен на фактический. Уголки, в которые был заключен фактический параметр, считаются не относящимися к параметру и при макроподстановке удаляются. Но в директиве IRP были свои уголки, именно они и сохранились в тексте, поэтому директива сохранила правильный синтаксис. Получившийся текст содержит конструкцию макроязыка, поэтому макрогенератор продолжает обрабатывать IRP-блок, пока не получится текст без макроподстановок:
До макрогенерации После макрогенерации
IRP R,<AX,SI,BP>
PUSH R
ENDM
PUSH AX
PUSH SI
PUSH BP
Аналогично создается макрос для подстановок команд POP.

Определение макроса через макрос
Из процедуры можно обращаться к другой процедуре. Аналогично, при описании одного макроса можно ссылаться на другой макрос. Допускается также обращение макроса к самому себе, то есть разрешены рекурсивные макросы. Но на практике рекурсивные макросы встречаются крайне редко. Рассмотрим поэтому нерекурсивное обращение одного макроса к другому.
Пусть макрос ARR предназначен для описания массива X из N байтов:
Assembler
1
2
3
ARR MACRO X,N
X DB N DUP (?)
ENDM
Используя его, можно определить макрос ARR2,предназначенный для описания сразу двух массивов одного и того же размера:
Assembler
1
2
3
4
ARR2 MACRO X1,X2,K
ARR X1,<K>
ARR X2,<K>
ENDM
При таком макроопределении макроподстановка для макроса ARR2 происходит в два этапа. В теле макроса ARR2 при обращении к макросу ARR необходимо второй фактический параметр записать в уголках:
До макрогенерацииПосле первой макрогенерацииПосле второй макрогенерации
ARR2 A,B,20
ARR A,<20>
ARR B,<20>
A DB 20 DUP (?)
B DB 20 DUP (?)
Если по смыслу первым и вторым параметрами макроса ARR2 могут быть только имена, то как третий параметр может быть указана достаточно сложная конструкция, например: ARR2 A,B,<25 MOD 10>.
Если бы вместо записи <K> использовалась просто запись K, тогда на первом этапе макроподстановки получилась бы макрокоманда ARR_A,25_MOD_10 с четырьмя операндами, а не с двумя (при макроподстановке уголки фактического параметра отбрасываются и в макрокомандах параметры могут отделяться как запятыми, так и пробелами). При записи <K> уголки заставляют рассматривать эту конструкцию как один параметр: ARR A,<25MOD10>.
В языке ассемблера допускается вложенность макроопределений. Но при этом макрос ARR, хотя и описан внутри макроса ARR2, не локализуется в ARR2, и к нему можно обращаться вне макроса ARR2. Но язык ассемблера заметит описание внутреннего макроса только при первом обращении к внешнему макросу.
Assembler
1
2
3
4
5
6
7
ARR2 MACRO X1,X2,K
ARR MACRO X,N
X DB N DUP (?)
ENDM
ARR X1,<K>
ARR X2,<K>
ENDM
Поэтому обращаться макросу ARR до обращения к макросу ARR2 нельзя:
Assembler
1
2
3
ARR A,50 ;ошибка (имя ARR еще не описано)
ARR2 B,C,100
ARR D,60 ;можно (имя ARR уже описано)
Директива LOCAL
При использовании макросов возникает проблема с метками, которые могут быть помечены предложения тела макроса. Если есть макрос M с внутренней меткой L и в программе имеется n обращений к этому макросу.
Assembler
1
2
3
4
5
M MACRO
...
L: ...
...
ENDM
Тогда после макроподстановок в окончательном тексте программы появится n команд, помеченные одной и той же меткой L, а это ошибка.
Макрокоманда Макрорасширение
M L: …
M L: …
Имя L не является формальным параметром макроса и поэтому при макроподстановке ни на что не заменяется, а переносится в макрорасширение без всяких изменений. Для избежания этой ошибки можно включить метку L в число параметров макроса и при обращении к макросу указывать различные фактические метки. Но метки, которыми метятся команды макроса, — это только внутреннее дело макроса, и для того, кто будет пользоваться таким макросом, придумывать имена для внутренних меток только лишняя головная боль. Поэтому в языке ассемблера принято более удобное решение данной проблемы (рисунок 68). В подобных случаях после директивы MACRO указывают директиву LOCAL v1, …,vk, где vi — имена, используемые в макроопределении (метки). При макроподстановке макрогенератор заменит эти имена на имена вида: ??xxxx, где — xxxx четырехзначное шестнадцатеричное число от ??0000 до ??FFFF.
Assembler
1
2
3
4
5
6
7
8
MD MACRO R1,R2
LOCAL M1,M2
M: CMP R1,R2
JB M1
SUB R1,R2
JMP M
M1:
ENDM
Директиву LOCAL можно указывать только после директивы MACRO любое число раз и нигде более. Директива LOCAL не переносится в макрорасширение.

Директивы EXITM и GOTO
У директивы EXITM нет операндов. Ее можно использовать только внутри макроопределений и блоков повторения (внутри конструкций макроязыка заканчивающихся директивой ENDM). Встретив директиву EXITM макрогенератор завершает обработку ближайшего объемлющего макроопределения или блока повторения.
Макрогенератор, создавая первую копию тела блока повторения, перенесет предложение DB 0 в макрорасширение, а затем, встретив EXITM, полностью завершит обработку этого блока, но не покинет тело макроса, а перескочит за ближайшую директиву ENDM. Директива EXITM используется тогда, когда при выполнении некоторого условия надо досрочно, не доходя до ENDM, прекратить макроподстановку или раскрутку блока повторения. Используется в основном в отладочных целях.
МакроопределениеМакрокомандаМакрорасширение
A MACRO K
REPT K
DB 0
EXITM
ENDM
DW ?
ENDM
A 5
DB 0
DW ?
Директива GOTO <имя метки> переводит процесс генерации макроопределения в другое место, прекращая тем самым последовательное разворачивание строк макроопределения. Метка, на которую передается управление, имеет специальный формат: :<имя метки>

Переопределение и отмена макросов
Если в тексте программы описать макрос с именем, которым ранее был обозначен другой макрос, то с этого момента прежний макрос считается уничтоженным, а новый макрос действующим:
МакроопределениеМакрокоманда Макрорасширение
A MACRO X
INCX
ENDM
A CX INC CX
A MACRO Y,Z
CMP Y,0
JE Z
ENDM
A BH,EQ
CMP BH,0
JE EQ
Макрос можно уничтожить, не определяя новый макрос с тем же именем, используя директиву PURGE с именем макроса (именами макросов через запятую). После этой директивы все макросы, имена которых в ней перечислены, считаются недействующими.
После директивы PURGE A,INIT к макросам A и INIT уже нельзя обращаться.

Условное ассемблирование
Условное ассемблирование — это средство для указания программе ассемблеру о трансляции или нетрансляции в объектный код конкретных блоков исходной программы. Если макросы и блоки повторений позволяют избежать многократного выписывания в исходном тексте программы повторяющихся фрагментов, то такое средство макроязыка, как условное ассемблирование, удобно при многократных прогонах программы. Условное ассемблирование позволяет в исходном тексте держать несколько вариантов одного и того же участка программы, а при каждом ее прогоне оставлять в окончательном тексте только один из фрагментов. Какой из вариантов будет оставлен, зависит от тех или иных условий, которые автор программы задает перед прогоном. Автор программы перед каждым прогоном не должен в ручную редактировать текст программы, а возлагает эту работу на макрогенератор. Вы можете в одном тексте программы создать и рабочую программу, и ее демонстрационную версию с ограниченным использованием функций рабочей программы. Возможно также создание в программе интерфейса, поддерживающего несколько языков для общения с пользователем и т.д. и т.п. Программист, изменяя несколько предложений в начале программы, указывает программе-ассемблеру, какие именно куски программы нужно включать в объектную программу для данной системы.
Участок программы, затрагиваемый условным ассемблированием, записывается в виде IF-блока:
<IF-директива>
<фрагмент1>
ELSE
<фрагмент2>
ENDIF
<IF-директива>
<фрагмент1>
ENDIF
Директивы ELSE и ENDIF обязательно должны записываться в отдельных строчках. В каждом фрагменте может быть любое количество любых предложений, в них допускаются вложенные IF-блоки.
В IF-директиве указывается некоторое условие, которое проверяется макрогенератором. Если условие выполнено, то макрогенератор оставляет в окончательном тексте программы только фрагмент1, а фрагмент2 исключает, не переносит в окончательный текст. Если же условие не выполнено, тогда фрагмент1 игнорируется, а в окончательную программу вставляется только фрагмент2. Если части ELSE нет, то считается, что пуст фрагмент2, поэтому при невыполнении условия такой IF-блок ничего не вставляет в окончательный текст программы.
Так как условие в IF-директиве проверяется на этапе макрогенерации, то в нем не должно быть ссылок на величины, которые станут известными только при выполнении программы (в условии нельзя ссылаться на содержимое регистров или ячеек памяти). Условие должно быть таким, чтобы макрогенератор мог вычислить его сразу, как только встретит его (не должно быть ссылок вперед).

Директивы IF и IFE
Встречая директиву IF<константное условие> или IFE <константное условие>, макрогенератор вычисляет указанное в ней константное условие. В директиве IF условие считается выполненным, если значение выражения не равно нулю, а в директиве IFE (if equal, если равно) если значение выражения равно нулю.
При отладке программы в определенные места текста программы вставляют печать промежуточных значений каких-то переменных, заканчивая отладку, отладочную печать убирают из текста программы. Но если в программе опять появляются ошибки, чтобы их найти, придется снова вставить отладочную печать, а после исправления опять удалить. И этот процесс вставки и удаления отладочной печати может продолжаться долго.
Вставлять и удалять отладочную печать можно самим, но если отладочной печати много и если она разбросана по всей программе, то такое изменение текста займет много времени, и здесь легко ошибиться. Понять это может только тот, кто сам прошел через отладочную распечатку своей программы. В подобной ситуации удобно использовать возможности условного ассемблирования: в тексте программы сохраняем отладочную печать, но перед ней указываем условие, что команды отладочной печати должны появляться в окончательном тексте программы, если установлена константа DEBUG EQU 1. Участок исходной программы с отладочной печатью должен выглядеть так:
Assembler
1
2
3
4
5
IF DEBUG
MOV AH,2
MOV DL,X
INT 21h
ENDIF
Пусть при отладке на экран выводится значение переменной X. Достаточно перед прогоном программы изменить константу DEBUG и макрогенератор будет формировать окончательный текст программы как с отладочной печатью, так и без нее. Сделать так, чтобы отладочная печать выполнялась при отладке и не выполнялась при счете, можно и с помощью команд условного перехода: получается то же самое решение проблемы.
Assembler
1
2
3
4
5
6
7
 MOV A,AX
CMP отладка
JNE L
MOV AH,2
MOV DL,X
INT 21h
L: ...
Но в данном случае команды отладочной печати остаются в программе всегда, нужны они или нет, и поэтому будут всегда занимать место в памяти. Проверка, выполнять отладочную печать или нет, делается в процессе выполнения программы и на это тратится время. И последнее, избыточный код — это всегда лазейка для взломщика. При условном ассемблировании команды отладочной печати, если они не нужны, будут удалены из окончательного варианта программы. Им просто не откуда взяться в памяти. Нет проверки, запускать отладочную печать или нет. При условном ассемблировании затрачивается больше времени на трансляцию программы, зато сама программа получается более экономичной и по памяти, и по времени.
Допустим, Вы описываете в виде макроса SHIFT X,N сдвиг значения в ячейке X на N разрядов вправо при условии, что параметр X — это имя какой-то переменной, чье значение находится в ячейке [110h], а N — явно заданное положительное число, при условии, что макрорасширение этого макроса должно содержать минимально возможное число байт.
Assembler
1
2
3
4
5
6
7
SHIFT MACRO X,N
IFE N-1
SHR X,1
ELSE
SHR X,N
ENDIF
ENDM
По макрокоманде SHIFT X,N при N=1 выработается код D02E1001 (SHR B/[0110],1), а при N>1 код C02E10010N (SHR B/[0110],0N), что на один байт больше. Поскольку в зависимости от величины N в окончательный текст программы должны попадать разные фрагменты, при описании макроса воспользуемся средствами условного ассемблирования.

Операторы отношения. Логические операторы
Константное выражение, указываемое в директивах IF и IFE, может быть любым, но по смыслу оно должно быть логическим выражением. В языке ассемблера существуют операторы, которые позволяют сформировать «чисто логический» результат. Это шесть операторов отношений результатом работы которых может быть одно из двух значений:
  • истина — число, которое содержит единицы во всех разрядах 0FFFFh;
  • ложь — число, которое содержит единицы во всех разрядах 0000h.
СинтаксисЗначение
<выражение>EQ<выражение> Equal = равно
<выражение>NE<выражение> Not Equal = не равно
<выражение>LT<выражение> Less Then = меньше чем
<выражение>LE<выражение> Less or Equal = меньше или равно
<выражение>GT<выражение> Greater Then = больше чем
<выражение>GE<выражение> Greater or Equal = больше или равно
Четыре логических оператора результатом работы которых может быть как логический, так и числовой результат:
Синтаксис Значение
NOT<константное выражение> Logical NOT = инверсия
<выражение>AND<выражение> Logical AND = конъюнкция
<выражение>OR<выражение> Logical OR = дизъюнкция
<выражение>XOR<выражение> logical eXclusive OR=исключающее ИЛИ
Логические значения и отношения могут объединяться в более сложные логические выражения. Логические операторы используются для записи операндов команд и директив и вычисляются еще на этапе трансляции программы. Операндами логических операторов могут быть любые константные (но не адресные), значения которых трактуются как 16-битные слова. Значением логических операторов является 16-битное слово, которое получается в результате поразрядного выполнения соответствующей операции.

Директивы IFB и IFNB, IFIDN, IFDIF
Директивы IFB и IFNB используются для проверки формальных параметров, передаваемых в макрос. При вызове макрокоманды они анализируют значение аргумента, и в зависимости от того, равно оно пробелу или нет, транслируется либо <фрагмент1>, либо <фрагмент2>. В директиве IFB <аргумент> (if blank, если пустой) условие считается выполненным, если значение аргумента равно пробелу, то есть фактический аргумент при вызове макрокоманды не был задан, а в директиве IFNB <аргумент> (if not blank, если не пустой) — если значение аргумента не равно пробелу.
Assembler
1
2
3
4
5
6
7
8
9
SAVE MACRO REGS
IFB <REGS>
display “не задан регистр”
EXITM
ENDIF
IRP R,<REGS>
PUSH R
ENDM
ENDM
Перед вами уже знакомый, но слегка модифицированный макрос SAVE. Если вызвать его без аргументов, то будет выведено сообщение о том, что не задан регистр и генерация макрорасширения будет прекращена директивой EXITM. Сообщение выводится при вызове макроса display с параметром “не задан регистр”.
Директивы IFIDN и IFDIF позволяют не просто проверить наличие или значение аргументов макрокоманды, но и выполнить идентификацию аргументов как строк символов. Директива IFIDN сравнивает аргумент1 и аргумент2 как строки символов. Если строки совпадают (identifity -тождество), то транслируется <фрагмент1> иначе <фрагмент2>. Директива IFIDNI — вариант, директивы IFIDN которая игнорирует различие между строчными и прописными буквами. Синтаксис этих директив:
IFIDN аргумент1, аргумент2
<фрагмент1>
ELSE
<фрагмент2>
ENDIF
Директива IFDIF сравнивает аргумент1 и аргумент2 как строки символов. Если строки не совпадают (differnt -другой), то транслируется <фрагмент1> иначе <фрагмент2>. Директива IFDIFI — вариант директивы IFDIF, которая игнорирует различие между строчными и прописными буквами. Синтаксис директив:
IFDIF аргумент-1, аргумент-2
<фрагмент1>
ELSE
<фрагмент2>
ENDIF
0
Закрытая тема Создать тему
Новые блоги и статьи
Модель заражения группы наркоманов
alhaos 17.04.2026
Условия задачи сформулированы тут Суть: - Группа наркоманов из 10 человек. - Только один инфицирован ВИЧ. - Колются одной иглой. - Колются раз в день. - Колются последовательно через. . .
Мысли в слух. Про "навсегда".
kumehtar 16.04.2026
Подумалось тут, что наверное очень глупо использовать во всяких своих установках понятие "навсегда". Это очень сильное понятие, и я только начинаю понимать край его смысла, не смотря на то что давно. . .
My Business CRM
MaGz GoLd 16.04.2026
Всем привет, недавно возникла потребность создать CRM, для личных нужд. Собственно программа предоставляет из себя базу данных клиентов, в которой можно фиксировать звонки, стадии сделки, а также. . .
Знаешь почему 90% людей редко бывают счастливыми?
kumehtar 14.04.2026
Потому что они ждут. Ждут выходных, ждут отпуска, ждут удачного момента. . . а удачный момент так и не приходит.
Фиксация колонок в отчете СКД
Maks 14.04.2026
Фиксация колонок в СКД отчета типа Таблица. Задача: зафиксировать три левых колонки в отчете. Процедура ПриКомпоновкеРезультата(ДокументРезультат, ДанныеРасшифровки, СтандартнаяОбработка) / / . . .
Настройки VS Code
Loafer 13.04.2026
{ "cmake. configureOnOpen": false, "diffEditor. ignoreTrimWhitespace": true, "editor. guides. bracketPairs": "active", "extensions. ignoreRecommendations": true, . . .
Оптимизация кода на разграничение прав доступа к элементам формы
Maks 13.04.2026
Алгоритм из решения ниже реализован на нетиповом документе, разработанного в конфигурации КА2. Задачи, как таковой, поставлено не было, проделанное ниже исключительно моя инициатива. Было так:. . .
Контроль заполнения и очистка дат в зависимости от значения перечислений
Maks 12.04.2026
Алгоритм из решения ниже реализован на примере нетипового документа "ПланированиеПерсонала", разработанного в конфигурации КА2. Задача: реализовать контроль корректности заполнения дат назначения. . .
КиберФорум - форум программистов, компьютерный форум, программирование
Powered by vBulletin
Copyright ©2000 - 2026, CyberForum.ru