ВУЗ: Не указан
Категория: Не указан
Дисциплина: Не указана
Добавлен: 24.12.2021
Просмотров: 12249
Скачиваний: 10

528
Глава 7. Уровень языка ассемблера
Листинг 7 . 1
. Код на языке ассемблера, в котором переменные р и q дважды
меняются местами (без использования макроса)
MOV EAX.P
MOV EBX.Q
MOV Q.EAX
MOV Р.ЕВХ
MOV EAX.P
MOV EBX.Q
MOV Q.EAX
MOV P.EBX
Листинг 7.2.
Тот же код с использованием макроса
SWAP MACRO
MOV EAX.P
MOV EBX.Q
MOV Q.EAX
MOV P.EBX
ENDM
SWAP
SWAP
Макрорасширение происходит во время процесса ассемблирования, а не во вре-
мя выполнения программы. Этот момент очень важен. Программы, приведенные
в листингах 7.1 и 7.2, произведут один и тот же машинный код. По программе на
машинном языке невозможно определить, использовались ли макросы при ее по-
рождении. После завершения макрорасширения ассемблер отбрасывает макрорас-
ширения. В полученной программе никаких признаков макросов не остается.
Макровызовы не следует путать с вызовами процедур. Основное различие
состоит в том, что макровызов — это команда ассемблеру заменить имя макроса
телом макроса. Вызов процедуры — это машинная команда, которая вставлена
в объектную программу и которая позднее будет выполнена, чтобы вызвать про-
цедуру. В табл. 7.6 сравниваются макровызовы и вызовы процедур.
Таблица 7.6.
Сравнение макровызовов и вызовов процедур
Макровызов
Вызов процедуры
Когда совершается вызов
программы?
Во время ассемблирования Во время выполнения
Да
Вставляется ли тело макроса
или процедуры в объектную программу
каждый раз, когда совершается вызов?
Команда вызова процедуры Нет
вставляется в объектную программу,
а затем выполняется?
Нужно ли после вызова использовать
команду возврата?
Сколько копий тела макровызова
или процедуры появляется
в объектной программе?
Нет
Одна на макровызов
Нет
Да
Да
1

Макросы
529
Можно считать, что процесс ассемблирования осуществляется в два прохода.
На первом проходе сохраняются все макроопределения, а макровызовы расширя-
ются. На втором проходе обрабатывается полученный в результате текст. Иными
словами, исходная программа считывается, а затем трансформируется в другую
программу, из которой удалены все макроопределения и в которой каждый макро-
вызов замещен телом макроса. Полученная программа без макросов затем посту-
пает в ассемблер.
Важно иметь в виду, что программа представляет собой цепочку символов. Это
могут быть буквы, цифры, пробелы, знаки пунктуации и «возврат каретки» (пере-
ход на новую строку). Макрорасширение состоит в замене определенных подце-
почек из этой цепочки другими цепочками. Макросредства — это способ манипу-
лирования цепочками символов безотносительно их значений.
Макросы с параметрами
Макросредства, описанные ранее, можно использовать для сокращения программ,
в которых часто повторяется точно одна и та же последовательность команд. Одна-
ко очень часто программа содержит несколько похожих, но не идентичных после-
довательностей команд (листинг 7.3). Здесь первая последовательность меняет
местами Р и Q, а вторая последовательность меняет местами R и S.
Листинг 7.3.
Почти идентичные последовательности команд
без использования макроса
MOV EAX.P
MOV EBX.Q
MOV Q.EAX
MOV P.EBX
MOV EAX.R
MOV EBX.S
MOV S.EAX
MOV R.EBX
Листинг 7.4.
Те же последовательности с использованием макроса
CHANGE MACRO P1.P2
MOV EAX.P1
MOV EBX.P2
MOV P2.EAX
MOV Pl.EBX
ENDM
CHANGE P.Q
CHANGE R.S
Для работы с такими почти идентичными последовательностями предусмот-
рены макроопределения, которые обеспечивают
формальные параметры,
и мак-
ровызовы, которые обеспечивают
фактические параметры.
Когда макрос рас-
ширяется, каждый формальный параметр, который появляется в теле макроса,
замещается соответствующим фактическим параметром. Фактические параметры
помещаются в поле операндов макровызова. В листинге 7.4. представлена програм-
ма из листинга 7.3, в которую включен макрос с двумя параметрами. Символы Р1

5 3 0 Глава 7. Уровень языка ассемблера
и Р2 — это формальные параметры. Во время расширения макроса каждый символ
Р1 внутри тела макроса замещается первым фактическим параметром, а символ
Р2 замещается вторым фактическим параметром. В макровызове
CHANGE P.Q
Р — это первый фактический параметр, a Q — это второй фактический параметр.
Таким образом, программы в листингах 7.3 и 7.4 идентичны.
Расширенные возможности
Большинство макропроцессоров содержат целый ряд расширенных особенностей,
которые упрощают работу программиста на языке ассемблера. В этом разделе мы
рассмотрим несколько расширенных особенностей MASM. Во всех ассемблерах
есть одна проблема: дублирование меток. Предположим, что макрос содержит ко-
манду условного перехода и метку, к которой совершается переход. Если макрос
вызывается два и более раз, метка будет дублироваться, что вызовет ошибку. По-
этому программист должен приписывать каждому вызову в качестве параметра
отдельную метку. Другое решение (оно применяется в MASM) — объявлять мет-
ку локальной (LOCAL), при этом ассемблер автоматически будет порождать другую
метку при каждом расширении макроса. В некоторых ассемблерах номерные мет-
ки автоматически считаются локальными.
MASM и большинство других ассемблеров позволяют определять макросы
внутри других макросов. Эта особенность очень полезна в сочетании с условной
компоновкой программы. Обычно один и тот же макрос определятся в обеих час-
тях оператора IF:
Ml MACRO
IF WORDSIZE GT 16 M2 MACRO
ENDM
ELSE
M2 MACRO
ENDM
ENDIF
tNDM
В любом случае макрос М2 б> дет определен, но определение зависит от того, на
какой машине ассемблируется программа: на 16-битной или на 32-битной. Если
Ml не вызывается, макрос М2 вообще не будет определен.
Наконец, одни макросы могут вызывать другие макросы, в том числе самих себя.
Если макрос рекурсивный, то есть вызывает самого себя, он должен передавать
самому себе параметр, который изменяется при каждом расширении, а также про-
верять этот параметр и завершать рекурсию, когда параметр достигает определен-
ного значения. В противном случае получится бесконечный цикл.
Реализация макросредств в ассемблере
Для реализации макросов ассемблер должен уметь выполнять две функции: со-
хранять макроопределения и расширять макровызовы. Мы рассмотрим эти функ-
ции по очереди.

Процесс ассемблирования 531
Ассемблер должен сохранять таблицу всех имен макросов, в которой каждое
имя сопровождается указателем на определение этого макроса, чтобы его можно
было получить в случае необходимости. В одних ассемблерах предусмотрена от-
дельная таблица для имен макросов, а другие содержат общую таблицу, в которой
находятся не только имена макросов, но и все машинные команды и директивы.
Когда встречается макроопределение, создается новый элемент таблицы с име-
нем макроса, числом параметров и указателем на другую таблицу — таблицу макро-
определений, где будет храниться тело макроса. Список формальных параметров
тоже создается в это время. Затем считывается тело макроса и сохраняется в таб-
лице макроопределений. Формальные параметры, которые встречаются в теле
цикла, указываются специальным символом.
Ниже приведен пример внутреннего представления макроса CHANGE. В каче-
стве символа возврата каретки используется точка с запятой, а в качестве символа
формального параметра — амперсант.
MOV EAX.&P1;MOV EBX.&P2:M0V &P2EAX;M0V &P1.EBX:
В таблице макроопределений тело макроса представляет собой просто цепочку
символов.
Во время первого прохода ассемблирования отыскиваются коды операций, а
макросы расширяются. Всякий раз, когда встречается макроопределение, оно со-
храняется в таблице макросов. При вызове макроса ассемблер временно приоста-
навливает чтение входных данных из входного устройства и начинает считывать
сохраненное тело макроса. Формальные параметры, извлеченные из тела макроса,
замещаются фактическими параметрами, которые предоставляются вызовом.
Амперсант перед параметрами позволяет ассемблеру узнавать их.
Процесс ассемблирования
В следующих разделах мы опишем, как работает ассемблер. И хотя на каждой ма-
шине есть свой определенный ассемблер, отличный от других, процесс ассембли-
рования по сути один и тот же.
Двухпроходной ассемблер
Поскольку программа на языке ассемблера состоит из ряда операторов, на первый
взгляд может показаться, что ассемблер сначала должен читать оператор, затем
транслировать его на машинный язык и, наконец, переносить полученный машин-
ный язык в файл, а соответствующий кусок листинга — в другой файл. Этот про-
цесс будет повторяться до тех пор, пока вся программа не будет оттранслирована.
Но, к сожалению, такая стратегия не работает.
Рассмотрим ситуацию, где первый оператор — переход к L. Ассемблер не мо-
жет ассемблировать это оператор, пока не будет знать адрес L. L может находиться
где-нибудь в конце программы, и тогда ассемблер не сможет найти этот адрес, не
прочитав всю программу. Эта проблема называется проблемой опережающей

5 3 2 Глава 7. Уровень языка ассемблера
ссылки,
поскольку символ L используется еще до того, как он определен (то есть
было сделано обращение к символу, определение которого появится позднее).
Опережающие ссылки можно разрешать двумя способами. Во-первых, ассемб-
лер действительно может прочитать программу дважды. Каждое прочтение исход-
ной программы называется
проходом,
а транслятор, который читает исходную
программу дважды, называется
двухпроходным транслятором.
На первом прохо-
де собираются и сохраняются в таблице все определения символов, в том числе
метки. К тому времени как начнется второй проход, значения символов уже извест-
ны, поэтому никакой опережающей ссылки не будет, и каждый оператор можно
читать и ассемблировать. При этом требуется дополнительный проход по исход-
ной программе, но зато такая стратегия относительно проста.
При втором подходе программа на языке ассемблера читается один раз и пре-
образуется в промежуточную форму, и эта промежуточная форма сохраняется
в таблице в памяти. Затем совершает второй проход, но уже не по исходной про-
грамме, а по таблице. Если физической памяти (или виртуальной памяти) доста-
точно для этого подхода, то будет сэкономлено время, затрачиваемое на процесс
ввода-вывода. Если требуется вывести листинг, тогда нужно сохранить полнос-
тью исходное выражение, включая комментарии. Если листинг не нужен, то про-
межуточную форму можно сократить, оставив только голые команды.
Еще одна задача первого прохода — сохранить все макроопределения и расши-
рить вызовы по мере их появления. Следовательно, в одном проходе происходит и
определение символов, и расширение макросов.
Первый проход
Главная функция первого прохода — построить
таблицу символов,
в которой со-
держатся значения всех имен. Символом может быть либо метка, либо значение,
которому с помощью директивы приписывается определенное имя:
BUFSIZE EQU 8192
Приписывая значение символьному имени в поле метки команды, ассемблер
должен знать, какой адрес будет иметь эта команда во время выполнения програм-
мы. Для этого ассемблер во время процесса ассемблирования сохраняет
счетчик
адреса команд (ILC — Instruction Location Counter)
(специальную переменную).
Эта переменная устанавливается на 0 в начале первого прохода и увеличивается
после каждой обработанной команды на длину этой команды (табл. 7.7.). Пример
написан для Pentium П. Мы не будем давать примеры для SPARC и Motorola, по-
скольку различия между языками ассемблера не очень важны и одного примера
будет достаточно. Кроме того, язык ассемблера для SPARC неудобочитаем.
При первом проходе в большинстве ассемблеров используется по крайней мере
3 таблицы: таблица символьных имен, таблица директив и таблица кодов операций.
В случае необходимости используется еще литеральная таблица. Таблица символь-
ных имен содержит один элемент для каждого имени, как показано в табл. 7.8.
Символьные имена либо используются в качестве меток, либо явным образом
определяются (например, с помощью EQU). В каждом элементе таблицы символьных