Cамоучитель по Assembler




Макросредства ассемблера


Современные ассемблеры содержат в себе так называемые макросредства и по этой причине называются иногда макроассемблерами. Общая идея макросредств заключается в том, что включением в исходный текст программы предложений специального языка макросредств (макроязыка) мы в какой-то степени управляем процессом трансляции программы. Макроязык позволяет выполнять или не выполнять трансляцию отдельных участков программы в зависимости от некоторого нами же определяемого условия (условная трансляция); осуществлять размножение участка исходного текста программы, в том числе, с модификацией каждого повторения (блоки повторения); включать в программу написанные отдельно фрагменты с настройкой их текста в соответствии с заданными параметрами (макрокоманды). Объекты, создаваемые с помощью директив макроязыка, обычно называют макросами. Иногда, правда, термин макрос относят только к одному конкретному виду макросредств, именно, к макрокоманде. Использование макросов упрощает составление исходного текста программы и иногда делает этот текст более наглядным, хотя в отдельных случаях, как, например, в случае директив условной трансляции, наоборот, может привести к существенному усложнению исходного текста.
Как и во всяком языке программирования, в языке макросредств имеется много разного рода тонкостей, но в прикладном программировании зачастую используются лишь базовые возможности этого языка. Поэтому мы ограничимся здесь рассмотрением основных макросредств ассемблера.
Блоки повторения
Блоки повторения заставляют транслятор повторить заданный блок исходного текста указанное число раз. Повторяемый блок может состоять из директив описания данных (и тогда он включается в состав сегмента данных) или из команд процессора (и тогда он описывается в программном сегменте). Например, следующий фрагмент сегмента данных позволяет образовать массив, состоящий из кодов ASCII прописных русских букв:

sym='A' ;Начальное значение временной переменной


symbols: ;Имя массива для ссылок на него


rept 32 ;Повторять столько раз


db sym ;Повторяемая директива


sym=sym+l ;Изменение переменной


endm ;Конец блока повторения

Как видно из приведенного фрагмента, блок повторения начинается с директивы ассемблера rept (от repetition, повторение), а заканчивается директивой endm (end macro, конец макроса). Реально в сегменте данных выделяется 32 байт, заполненных числами от 81h до 9Fh, которые предполагается рассматривать, как последовательность русских букв. Того же результата можно было достигнуть с помощью следующего предложения:

symbols db "А", "Б", "В", "Г", и т.д. до буквы Я


или проще, хотя и менее наглядно:

symbols db 128,129,130,131, и т.д. до числа 159.


Макрос повторения несколько сокращает время, требуемое для описания в тексте программы требуемого массива, хотя, возможно, снижает наглядность этого описания.
При подключении к компьютеру измерительного или управляющего оборудования иногда возникает необходимость замедлить работу процессора при обращении к портам этого оборудования. Замедление осуществляется включением в текст программы одной или, если требуется, нескольких команд безусловного перехода на следующее предложение:

in AL,300h ;Первое обращение к оборудованию


jmp a ;Задержка на время


a: jmp b ;выполнения


b: jmp с ;трех команд jmp


c: in AL,301h ;Следующее обращение к оборудованию

Для того, чтобы не создавать много ненужных, в сущности, меток, такого рода предложения часто записывают следующим образом:

in AL, 300h ;Первое обращение к оборудованию


jmp $+2 ;Задержка на время


jmp $+2 ;выполнения


jmp $+2 ;трех команд jmp


in AL,301h ;Следующее обращение к оборудованию

Здесь используется обозначение счетчика текущего адреса S. При трансляции любой команды в счетчике текущего адреса содержится адрес этой команды (смещение ее первого байта). Команда короткого перехода занимает 2 байт, поэтом}' команда jmp $+2 осуществляет переход на команду, идущую следом.
Часто в подобных случаях ограничиваются одной командой jmp, которая создает необходимую задержку в доли микросекунды. В тех случаях, однако, когда устройство сопряжения с оборудованием работает заметно медленнее процессора, приходится включать между командами обращения к портам 5-6 команд jmp. Такой фрагмент можно оформить в виде блока повторения:

rept 6 jmp $+2 endm

Это, пожалуй, проще, чем писать 6 команд jmp.
Макросы повторения имеют несколько разновидностей, которые мы не будем здесь рассматривать.
Макрокоманды
Программы, написанные на языке ассемблера, часто содержат повторяющиеся участки текста с одинаковой структурой. Такой участок текста можно оформить в виде макроопределения, характеризующегося произвольным именем и необязательным списком формальных аргументов. После того, как такое определение сделано, появление в программе строки, содержащей имя макроопределения и список фактических аргументов (все это вместе называют макрокомандой), приводит к генерации всего требуемого текста, называемого макрорасширением. Варьируя фактические аргументы, можно, сохраняя неизменной структуру макрорасширения, изменить отдельные его элементы.
Макроопределение должно начинаться строкой с именем макроопределения и директивой macro, в поле аргументов которой указывается список формальных аргументов. Заканчивается макроопределение директивой endm.
Пусть в программе требуется неоднократно сохранять в стеке содержимое трех регистров, но в каждом конкретном случае номера регистров и их порядок отличаются. Оформим эти действия в виде макроопределения:

psh macroa,b,c

push a


push b


push с


endm

Появление в исходном тексте программы строки

psh АХ, ВХ, СХ

приведет к генерации следующего фрагмента текста:

push AX push BX push CX

Если же в исходном тексте имеется строка

psh DX, ES, ВР

то соответствующее макрорасширение будет иметь вид:

push DX


push ES


push BP

В качестве фактических аргументов могут выступать любые обозначения ассемблера, допустимые для данной команды. В частности, макровызов

psh mem, [BX], ES: [17h]

приведет к следующему макрорасширению:

push mem


push [BX]


push ES : [17h]

Если какие-то строки макроопределения должны быть помечены (например, с целью организации циклов), то обозначения меток следует объявить локальными с помощью оператора local. В этом случае ассемблер, генерируя макрорасширения, будет создавать собственные обозначения меток, не повторяющиеся при повторных вызовах одной и той же макрокоманды:

delay macro


local point


mov CX,200


point: loop point


endm

Макрос delay создает задержку фиксированной длительности. Если в текст программы включить две макрокоманды delay



delay



delay

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


mov CX, 20000


??0000: loop ??0000



mov CX, 20000


??0000: loop ??0000

При повторных подстановках макроопределения транслятор заменяет обозначение метки point на различающиеся обозначения ??0000, ??0001 и т.д., обеспечивая тем самым правильное выполнение команд циклов и переходов.
Макрокоманды схожи с подпрограммами в том отношении, что в обоих случаях мы описываем некоторый программный фрагмент один раз, а обращаемся к нему многократно, возможно, с передачей различных параметров. Однако эти вычислительные средства различаются как по способу использования, так и по своим возможностям.
Подпрограммы позволяют сократить объем выполнимого файла за счет описания повторяющихся участков программы лишь однажды. При каждом вызове подпрограммы командой call происходит переход на один и тот же фрагмент программы, содержащий подпрограмму, а после выполнения подпрограммы - возврат назад в точку вызова. Текст подпрограммы полностью определяется на этапе ее написания, и изменения в ходе выполнения подпрограммы возможны только за счет передачи ей тех или иных конкретных значений.
Механизм использования макроса иной. Каждая макрокоманда, встретившаяся транслятору в тексте программы, заменяется им на полный текст макроопределения. Если макрокоманда содержит параметры, то в процессе этой замены происходит подстановка параметров в текст макроопределения. Образованное таким образом макрорасширение составляет часть текста программы, неотличимо от остальных предложений программы и не нуждается в каких-либо вызовах. В силу этих обстоятельств макрокоманды оказываются несколько эффективнее подпрограмм по скорости выполнения, особенно, если учесть время, требуемое для подготовки параметров перед вызовом подпрограммы (например, проталкивание их в стек). Вряд ли стоит, однако, проводить такое сравнение. Подпрограммы и макрокоманды имеют различные области применения.
Подпрограммы служат для сокращения объема программы, повышения ее наглядности и упрощения перестройки алгоритма выполнения всего программного комплекса путем изменения состава и порядка вызываемых подпрограмм. При этом активное использование подпрограмм может уменьшить размер всей программы в десятки раз.
Смысл использования макрокоманд совсем иной. Макрокоманды позволяют упростить процесс написания программы и, можно сказать, являются средством автоматизации программирования. При этом язык макрокоманд предоставляет большие возможности по изменению текста макрорасширения в зависимости от указываемых в макрокоманде параметров. Проиллюстрируем эти возможности на простом примере макрокоманды вывода на экран символа. Такой макрокомандой можно пользоваться в процессе отладки сложных программ, чтобы получать информацию о содержимом любых ячеек памяти. Пример оформлен в виде законченной программы, которая носит чисто демонстрационный характер.

;Пример 2-1. Использование макрокоманды
sym macroc ;Имя и формальный аргумент
push AX ;Сохраним используемые
push DX ;в макроопределении регистры
mov АН, 02h ;Функция DOS вывода символа
mov DL,c ;Заберем символ
int 21h ;Вызов DOS
pop DX ;Восстановим
pop AX ;регистры
endm ;Конец макроопределения
code segment
assume cs:code
main proc
sym 'w' ;Символ указан непосредственно
sym ES : 0 ;Вывод первого байта PSP
sym CS:msg ;Вывод первой буквы из msg
lea BX,msg-t-l ;Адрес второй буквы из msg
sym [BX] ;Вывод второй буквы
mov AX, 40h ;Настроим DS
mov DS,AX ;на начало памяти
sym DS:49h ;Вывод номера видеорежима
mov AX,4C00h ;Завершение программы
int 21h
main endp
msg db 'OK'
code ends

Тексты макроопределений обычно размещаются в самом начале программы, что дает возможность вызывать макрокоманды из любых точек программы. Содержательная часть макроса sym состоит в вызове функции 02h DOS, которая выводит на экран символ из регистра DL. Поскольку макрос использует регистры АХ и DX, они в начале макроса сохраняются в стеке, а перед его завершением восстанавливаются. В качестве параметра макрокоманды можно использовать любое обозначение ассемблера, которое может интерпретироваться, как адрес символа.
Сама программа умышленно построена несколько нестандартным образом. В ней имеется единственный сегмент с текстом программы, в конце которого помещена строка данных (слово 'ОК'). Такое расположение данных допустимо, однако для обращения к ним необходимо использовать замену сегмента (как это сделано в третьей строке программы), так как программный сегмент адресуется через регистр CS. Сегмент стека в программе отсутствует, что не очень хорошо, но для небольших программ допустимо. Фактически под стек будет использован самый низ сегмента команд, начиная с адреса FFFEh. Поскольку наша программа имеет размер, существенно меньше 64К, такое расположение стека не приведет ни к каким неприятностям (при большом размере программы стек мог бы начать затирать нижние строки программы).
В программе проиллюстрировано использование в качестве фактического аргумента макрокоманды различных конструкций языка: непосредственного обозначения символа (что, наверное, лишено смысла), прямого обращения к различным участкам памяти по абсолютным адресам через регистры ES и DS, адресации с использованием символического обозначения поля данных. На Рисунок 2.18 приведен вывод программы.









Начало  Назад  Вперед