В данной главе рассматриваются некоторые особенности ассемблера, которые мало связаны с основным процессом ассемблирования, заключающимся в генерации машинного кода. Макроинструкции являются средством порождения больших и сложных фрагментов текста программы, осуществляемого с помощью всего лишь нескольких простых операторов. Условное ассемблирование позволяет получать различные варианты генерируемого кода в зависимости от управляющих параметров, посредством которых можно влиять на структуру всей программы в целом или на расширение макроинструкций. Фактически средства макрообработки включены во все современные языки ассемблера. По сути макроинструкции представляют собой поименованные блоки текста программы, в которых допускается параметрическая подстановка и которые могут быть вставлены в различные места программы. Возможность макрообработки не является особенностью, свойственной только для языка ассемблера, поскольку любой язык программирования может быть функционально расширен для работы с поименованными блоками текста программы. Тем не менее макрообработка включена лишь в некоторые языки высокого уровня, поэтому она чаще всего ассоциируется с языком ассемблера.
В этой главе читателю будут даны основы написания макроинструкций и использования условного ассемблирования. Однако эти темы весьма сложные и материал данной главы является только начальным их изложением. В гл. 11 будет показано, что имеется большое число макроинструкций, написанных для того, чтобы помочь программистам в использовании системных сервисных средств (system services) операционной системы VAX/VMS. Системные сервисные средства представляют собой программы и подпрограммы, позволяющие пользователю выполнять системно-ориентированные функции, например ввод и вывод. Макроинструкции, позволяющие обращаться к системным сервисным средствам, или системные макроинструкции содержатся в библиотеке, доступной для программистов, работающих с языком ассемблера. Чаще всего эти макроинструкции применяются для генерации вызывающих последовательностей и списков аргументов системных сервисных программ. Одной из таких макроинструкций, уже использованной ранее в тексте книги, является $EXIT_S, с помощью которой формируется вызов подпрограммы SYS$EXIT.
При программировании задачи на языке ассемблера часто обнаруживается, что большие фрагменты программы неоднократно повторяются. Первое, что при этом приходит в голову, - избавиться от повторений, применяя циклы и подпрограммы. Хотя в принципе это верно, есть ситуации, когда циклы и подпрограммы - не лучшее решение. Ниже перечислены некоторые возможные соображения, по которым иногда нежелательно использование циклов и подпрограмм.
Выше приведены далеко не все возможные соображения, но и они позволяют получить некоторое представление о том, почему предпочтительнее повторение фрагментов программы, чем использование циклов. Именно эти соображения явились предпосылками для создания макроассемблеров.
В языке ассемблера простейший тип повторения представляет блок повторения. Блок повторения - это фрагмент текста программы, повторяемый несколько раз без каких-либо изменений. Примером того, когда в этом возникает необходимость, является случай, приведённый выше, - массив, заполненный пятёрками. Его можно сформировать, поместив одну за другой несколько строчек .LONG 5.
Для удобства пользователя в языке ассемблера ЭВМ семейства VAX имеется специальная директива указания повторений. Директива .REPT используется в контексте
.REPT ВЫРАЖЕНИЕ . . БЛОК КОДА . .ENDR
Фрагмент текста программы будет повторён много раз, причём количество повторений определяется значением выражения, следующего за директивой .REPT. На рис. 10.1,а показано, каким образом можно сформировать блок из семи длинных слов, содержащих число 5, используя директиву .REPT. На рис. 10.1,б представлен эквивалентный текст программы. Заметим, что, хотя в примере между директивами .REPT и .ENDR расположена только одна строка, число строк не ограничивается[1] и фрагмент текста программы может иметь любую длину.
(а) Блок повторения |
(б) Эквивалентный код |
---|---|
.REPT 7 .LONG 5 .ENDR |
.LONG 5 .LONG 5 .LONG 5 .LONG 5 .LONG 5 .LONG 5 .LONG 5 |
Рис. 10.1. Использование блока повторения
Обратите также внимание на то, что строки в блоке повторения повторяются без изменений. Никакие изменения не вносятся в строки текста программы. Это не означает, однако, что генерируемый машинный код также не может быть изменён. Для генерации различного машинного кода можно использовать выражения и определения, что и будет показано в следующем разделе.
Предположим, что программисту требуется создать массив из 100 пар длинных слов. Первое длинное слово в каждой паре содержит начальный адрес следующей пары длинных слов. Второе длинное слово первоначально содержит 0. Такая организация данных известна как однонаправленный список и часто используется для данных, подлежащих переупорядочиванию. Для переупорядочивания данных в этом случае требуется переместить только некоторые указатели (адреса). Сами слова данных остаются на своих местах.[2]
Такая структура данных может быть создана с помощью операторов
10$: .ADDRESS 20$ .LONG 0 20$: .ADDRESS 30$ .LONG 0 30$: .ADDRESS 40$ .LONG 0 40$: .ADDRESS 50$ .LONG 0 50$: . . .
Очевидно, что здесь имеет место повторяющаяся структура, однако эго не точное повторение, поскольку все адреса различны. Для того чтобы реализовать указанную структуру с помощью блоков повторения, используется специальный знак . (точка). Точка обозначает значение счётчика адреса. Как упоминалось в гл. 4, счётчик адреса ассемблера содержит адрес следующей доступной ячейки памяти. Поскольку адрес ячейки постоянно меняется, то изменяется и значение, приписываемое точке. Например, при ассемблировании длинных слов значением, присвоенным точке, является адрес длинного слова, ассемблируемого в текущий момент. Адрес следующего длинного слова в памяти может быть обозначен выражением .+4. Следующее затем длинное слово будет иметь адрес (.+8). Как можно сформировать структуру такого связного списка, показано на рис. 10.2.
.REPT 100 .ADDRESS .+8 .LONG 0 .ENDR
Рис. 10.2. Структура связного списка
Несмотря на то, что часто переменные данные можно представить в виде сложных выражений, включающих знак точки, есть случаи, когда это сделать трудно, почти невозможно. Тогда может оказаться полезным другой способ определения переменных данных. Он связан с использованием знака равенства (=). Знак равенства уже употреблялся ранее при определении параметров в выражениях вида SIZE=10. Большей частью определения имён, заданных с помощью знаков '=' и ':', подобны. Но есть и существенное различие. Если имя определяется более одного раза с использованием знака ':', то имеет место ошибка повторного определения; в этом случае программа будет сопровождаться сообщением об ошибке. В отличие от этого имена, определённые знаком =, могут быть переопределены в дальнейшем очередным знаком '='. Эти определения повторяются на двух проходах ассемблера, и новое значение имени будет распространяться на строки программы, следующие после его переопределения.
На рис. 10.3 показано, как структура рис. 10.2 может быть реализована при переопределении имени с использованием знака = вместо знака '.'. Хотя этот пример может показаться в чём-то более сложным, его преимущество заключается в большей общности. Рассмотрим, например, заполнение массива из восьми длинных слов значениями факториалов от 1 до 8. Переопределение в блоке повторения является простым и эффективным способом создания такого массива (рис. 10.4).
Отметим, что имена K и F на рис. 10.4 не являются ни ячейками памяти, ни даже их именами. Это просто имена, которым соответствуют числовые значения в таблице имён. Они не используются в качестве адреса инструкции или элемента данных. Заметим также, что вычисления типа K=K+8, K=K+1 или F=F*K производятся во время ассемблирования программы, а не во время её выполнения. Данные строки программы не вызывают порождение какого-либо выполняемого кода.
Эта особенность языка ассемблера может вносить путаницу, поскольку в языках высокого уровня, таких как Фортран или Паскаль, выражения K=K+8 или K:=K+8; рассматриваются как выполняемые операторы, которые транслируются в инструкции, такие как ADDL2 #8,K. В отличие от этого в языке ассемблера оператор K=K+8 задаёт операцию над значениями, содержащимися в таблице имён, осуществляемую во время ассемблирования и не требующую вычислений во время выполнения программы.
Блок повторения |
Эквивалент блока повторения |
Эквивалент с вычисляемым K |
---|---|---|
K=0 A: .REPT 100 K=K+8 .ADDRESS A+K .LONG 0 .ENDR |
K=0 A: K=K+8 .ADDRESS A+K .LONG 0 K=K+8 .ADDRESS A+K .LONG 0 K=K+8 .ADDRESS A+K .LONG 0 . . . |
A: .ADDRESS A+8 .LONG 0 .ADDRESS A+16 .LONG 0 .ADDRESS A+24 .LONG 0 |
Рис. 10.3. Построение связного списка с переопределением имён
K=1 F=1 .REPT 8 .LONG F K=K+1 F=F*K .ENDR
Рис. 10.4. Массив факториалов
Это отличие легче понять в том случае, когда имена представляют собой адреса. В языке высокого уровня результатом выполнения оператора вида K=L+4 (или K:=L+4) будет то, что содержимое ячейки с адресом K станет равно содержимому ячейки с адресом L+4. В языке ассемблера результатом вычисления K=L+4 будет то, что K станет адресом длинного слова, расположенного после длинного слова L.
В предшествующих главах использовались символические выражения простейшего вида. Обычно такие выражения сводились к выражениям вида A-6 или A+6, означающим адрес ячейки, расположенной на шесть ячеек перед или после ячейки A. При использовании блоков повторения, макроинструкций и условного ассемблирования нужны более сложные выражения.
Прежде всего следует вспомнить, что необязательно применять имена только для задания адресов. Символические имена могут применяться для обозначения чисел, которые могут быть использованы для любых целей.
Программист, пишущий программы на языке ассемблера, должен хорошо уяснить связь между самим процессом ассемблирования и выполнением программы. Например, как было упомянуто выше, строка ассемблера K=K+1 не означает, что во время выполнения программы значение K будет увеличено на 1. Как уже было сказано, для этого оператора не будет сгенерирован какой-либо исполняемый машинный код. В данном случае будет увеличено на 1 значение, соответствующее имени K в таблице имён ассемблера. Не следует путать оператор K=K+1 с инструкцией INCL K. Это совершенно разные вещи.
Очень важно отметить, что существует разница между именами для значений, изменяющихся при перемещении программы, именами для значений, которые при этом не изменяются, и глобальными именами, определёнными в других модулях. Например, именам, обозначающим адреса в текущем модуле, соответствуют значения, которые должны измениться при настройке программы на область памяти, где она будет выполняться. Такие имена называются переместимыми. Значения имён, применяемых для обозначения чисел, при перемещении не меняются. Кроме того, имеются адреса, значение которых фиксировано. Эти адреса называются абсолютными адресами и используются операционной системой для доступа к ячейкам специального назначения, которые описываются в гл. 15. Имена, соответствующие значениям, не изменяемым при перемещении программы, называются абсолютными. Имена в таблице имён делятся на переместимые и абсолютные. К именам третьего типа относятся глобальные имена, но поскольку они обрабатываются специальным образом, то рассматриваться не будут.
В языке ассемблера выражения могут использоваться практически во всех случаях, когда требуется задание числа или значения. Выражения формируются в виде комбинации имён и чисел со знаками математических операций +, -, * и /, почти так же, как в языках Паскаль или Фортран. Допускается использование скобок, за тем исключением, что в качестве левых и правых скобок используются угловые скобки (< и >). Важно отметить, что нет приоритетности операций. Выражения вычисляются слева направо, при этом скобки действуют обычным образом. Например, выражение X+Y*<A+<5*B/K>> является допустимым выражением. Поскольку вычисления ведутся слева направо, то выражение 1+2*3 равно 9, а не 7. (В ассемблерах некоторых других ЭВМ не придерживаются этих правил.)
Выражения имеют те же типы, что и имена. Вместо того, чтобы перечислять исчерпывающий набор правил для определения типов выражений, далее даётся набор правил на уровне здравого смысла, которыми можно руководствоваться всегда, за исключением наиболее нестандартных ситуаций[3].
Приведённые выше правила могут, разумеется, применяться и к частям сложных выражений. Кроме того, существуют два правила, относящиеся к операциям умножения и деления.
;ПОДПРОГРАММА MPRINT - ПЕЧАТЬ СООБЩЕНИЯ В РАМКЕ ; .TITLE MPRINT ПЕЧАТЬ СООБЩЕНИЯ .EXTERNAL PLINE MARK=^A"*" BOXES=3 MSG: .ASCII "ВЫВОД СООБЩЕНИЯ" EMSG: MSG_LENGTH=EMSG-MSG+<2*BOXES> MSG_BUF: .BLKB MSG_LENGTH ; .ENTRY MPRINT,^M<R2> BSBW HORIZ ; ПЕЧАТЬ ГОРИЗОНТАЛЬНОГО ОБРАМЛЕНИЯ MOVAB MSG_BUF,R1 ; ПОМЕСТИТЬ В БУФЕР BSBW VERT ; НАЧАЛО ВЕРТИКАЛЬНОГО ОБРАМЛЕНИЯ MOVAB MSG,R0 MOVAB EMSG,R2 ; R2 - АДРЕС КОНЦА СТРОКИ 10$: MOVB (R0)+,(R1)+ ; ПЕРЕСЫЛКА СИМВОЛОВ ИЗ MSG В BUFFER CMPL R0,R2 ; ПРОВЕРКА НА КОНЕЦ СТРОКИ BNEQ 10$ ; ПОМЕСТИТЬ В БУФЕР BSBW VERT ; КОНЕЦ ВЕРТИКАЛЬНОГО ОБРАМЛЕНИЯ BSBW PBUF ; ПЕЧАТЬ СТРОКИ СООБЩЕНИЯ BSBW HORIZ ; ПЕЧАТЬ ГОРИЗОНТАЛЬНОГО ОБРАМЛЕНИЯ RET ; HORIZ: MOVL #BOXES,R2 ; R2 - СЧЁТЧИК СТРОК 10$: CLRL R0 ; R0 - ИНДЕКС В БУФЕРЕ 20$: MOVB #MARK,MSG_BUF[R0] ; ПОМЕСТИТЬ СИМВОЛ MARK В БУФЕР AOBLSS #MSG_LENGTH,R0,20$ ; ПОВТОРЯТЬ ДО ЗАПОЛНЕНИЯ БУФЕРА BSBW PBUF ; ПЕЧАТЬ СТРОКИ C ЗАПОЛНИТЕЛЕМ MARK SOBGTR R2,10$ ; ПЕЧАТЬ #BOXES СТРОК RSB ; VERT: MOVL #BOXES,R0 ; ВСТАВИТЬ #BOXES СИМВОЛОВ MARK 10$: MOVB #MARK,(R1)+ ; R1 - УКАЗАТЕЛЬ БУФЕРА SOBGTR R0,10$ ; ПОВТОРЯТЬ ПОКА, HE ВСТАВЯТСЯ ВСЕ #BOXES RSB ; PBUF: PUSHR #^M<R0,R1> ; СОХРАНИТЬ R0 И R1 В СТЕКЕ MOVAL MSG_BUF, R0 ; R0 - УКАЗАТЕЛЬ БУФЕРА MOVZWL #MSG_LENGTH,R1 ; R1 СОДЕРЖИТ ДЛИНУ JSB PLINE ; ПЕЧАТЬ СТРОКИ POPR #^M<R0,R1> ; ВОССТАНОВИТЬ R0 И R1 RSB ; .END
Рис. 10.5. Подпрограмма печати сообщений
Обычно при программировании на языке ассемблера важно иметь возможность использовать выражения и имена вместо чисел, поскольку это даёт программисту более простой способ модификации программы, а также облегчает понимание и документирование программы. В качестве примера рассмотрим программу, которая распечатывает сообщение и окружает его тройной рамкой из звёздочек:
************************* ************************* ************************* *** ВЫВОД СООБЩЕНИЯ *** ************************* ************************* *************************
Это делается для того, чтобы сообщение сразу бросалось в глаза. На рис. 10.5 представлена программа печати такого сообщения.
Если использовать имена и выражения, то внесение изменений в программу становится довольно простым. Например, если сообщение должно быть окружено пятью рамками из звёздочек вместо трёх, то единственное, что требуется изменить, - это шестая строка программы, которая теперь будет выглядеть так: BOXES=5. Если требуется заменить звёздочки на знаки @ то достаточно заменить пятую строку программы на MARK=^A"@". Оператор ^A используется для того, чтобы определить значение в коде ASCII для следующей строки символов. Таким образом, выражение ^A"@" задаёт значение кода ASCII для знака @, равное ^X40. Подразумевается, что в программе используется глобальная подпрограмма PLINE для распечатки строки символов, адрес строки передаётся ей через регистр R0.
Последний момент, связанный с выражениями и их применением, который будет рассмотрен, касается очерёдности определения и использования имён. Вспомним, что два прохода в процессе ассемблирования требуются потому, что в инструкции может быть ссылка на символический адрес, который определён в программе позднее. Однако имеются такие случаи, когда значение должно быть известно на первом проходе. Тогда имена, используемые в выражениях для вычисления таких значений, должны быть определены заранее.
По сути поскольку таблица имён создаётся на первом проходе, то все имена, влияющие на значения в таблице имён, должны быть определены до своего использования. Простым примером такой ситуации является случай непосредственного определения, который обычно приводит к ошибке. Например, при
A=B B=5
переменная A останется неопределённой после завершения первого, а также в начале второго прохода. Разумеется, эта проблема будет легко устранена, если эти две строки поменять местами.
Более сложная проблема возникает, когда выражение может повлиять на объём программы. Если в таком выражении содержатся неопределённые имена, то это вызывает ошибку в вычислении всех последующих адресов в остальной части программы. Например,
X: .BLKL A Y: .BLKL 1 A=5
Поскольку переменная A не определена на первом проходе, то ассемблеру не будет известно, сколько длинных слов нужно зарезервировать под массив X. За неимением лучшего ассемблер присваивает для неопределённых имён нулевое значение. Поэтому адрес Y будет таким же, как адрес X. Однако на втором проходе будет известно, что значение A равно 5, так что адрес Y окажется на 20 больше, чем адрес X. Это расхождение будет отмечено сообщением типа Expression is not absolute ("Выражение не является абсолютным"). Причиной появления такого сообщения является то, что в ассемблере подразумевается, что неопределённые выражения представляют собой глобальные, или переместимые имена, которые будут определены в дальнейшем.
Кроме того, всякий раз, когда выражение используется таким образом, что его значение должно быть известно во время ассемблирования, это выражение должно не только состоять целиком из предварительно определённых имён, но и быть настолько простым, чтобы ассемблер мог вычислить его значение. Очевидно, что поэтому не могут быть использованы сложные выражения, которые должны вычисляться компоновщиком.
а. |
.REPT 5 .ADDRESS .+5 .ENDR |
б. |
.REPT 7 .LONG 5 .ADDRESS .-4 .ENDR |
в. |
K=0 .REPT 4 K=K+4 .ADDRESS .+K .ENDR |
г. |
K=0 .REPT 5 K=K+1 .REPT K .LONG K*3 .ENDR .ENDR |
а. | I*J+K |
б. | I+J*K |
в. | J/I+2 |
г. | I+J/K |
д. | I+<J*K>/K |
е. | I+<<J*K>+K> |
ж. | I+J*<I+J> |
з. | <I+J>*I+J |
а. | A+I |
б. | B-K |
в. | A-B |
г. | A+B |
д. | A+K-B |
е. | <A-B>+<A-C> |
ж. | <A-K>+<A-J> |
з. | <A+K>*<<B-C>*4> |
I=3 A: .BLKL K B: .BLKL D-C C: .BLKL I D: .BLKL K+I K=J J=I+5 .END
Макроинструкции - это сложные блоки программного текста, которые могут быть многократно повторены в программе. В отличие от блоков повторения повторяющийся код, генерируемый с помощью макроинструкций, не обязательно должен весь находиться в одном месте, а может быть помещён в различных точках программы. К тому же в отличие от блоков повторений макроинструкции позволяют вносить существенные изменения в повторяющийся код.
В сущности, макроинструкции являются поименованными блоками текста программы. Везде, где в поле операции указывается имя макроинструкции, в тело программы будет вставлен (скопирован) соответствующий блок программного текста. Кроме того, в макроинструкции могут присутствовать параметры или имена, которые модифицируются или подставляются всякий раз, когда макроинструкция используется или, другими словами, происходит её вызов.
Для примера заметим, что на ЭВМ семейства VAX не предусмотрена инструкция сложения 64-разрядных целых чисел. Предположим, что при решении некоторой задачи потребуется арифметика с двойной точностью (см. гл. 6). Поэтому при сложении квадраслов необходимо иметь эквивалент следующим строкам:
ADDL3 A,B,C MOVL A+4,C+4 ADWC B+4,C+4
Было бы удобно, чтобы такой блок генерировался при появлении имени ADDQ3. Для этого сначала нужно определить этот блок как макроинструкцию с именем ADDQ3. Имена A, B и C будут использоваться как имена параметров. Можно применять любые произвольные символические имена, за исключением тех, которые уже задействованы в программе.
После того как будут выбраны имена, требуется определить макроинструкцию. Описание начинается с директивы .MACRO ADDQ3 A,B,C, за которой идут строки текста программы, которые будут генерироваться всякий раз при вызове макроинструкции. Для обозначения конца макроопределения используется директива .ENDM ADDQ3. В строке директивы .ENDM записывается имя макроинструкции ADDQ3, что помогает ассемблеру и программисту в использовании вложенных макроинструкций, рассмотренных ниже. На рис. 10.6 представлено полное макроопределение.
.MACRO ADDQ3 A,B,C ADDL3 A,B,C MOVL A+4,C+4 ADWC B+4,C+4 .ENDM ADDQ3
Рис. 10.6. Простое макроопределение
Само по себе макроопределение не приводит к появлению в программе какого-либо нового текста, текст генерируется только при вызове макроинструкции. Макровызов состоит из имени макроинструкции, используемого так, как если бы это был код операции, после которого идут аргументы, которые должны быть подставлены вместо параметров макроопределения. На рис. 10.7 представлено несколько примеров макровызовов и порождаемого при их вызове программного кода. На рис. 10.7, п.а показан простой и понятный макровызов, в котором имена X, Y и Z подставляются вместо параметров A, B и C. На рис. 10.7, п.б имена A, B, C подставляются вместо самих себя, это не приводит ни к каким недоразумениям.
|
Макровызов |
Генерируемый код |
---|---|---|
а. |
ADDQ3 X,Y,Z |
ADDL3 X,Y,Z MOVL Х+4,Z+4 ADWC Y+4,Z+4 |
б. |
ADDQ3 A,B,C |
ADDL3 A,B,C MOVL A+4,C+4 ADWC B+4,C+4 |
в. |
ADDQ3 COST,PROFIT,PRICE |
ADDL3 COST,PROFIT,PRICE MOVL COST+4,PRICE+4 ADWC PROFIT+4,PRICE+4 |
г. |
ADDQ3 X+4,Y+8,Z+12 |
ADDL3 X+4,Y+8,Z+12 MOVL X+4+4,Z+12+4 ADWC Y+8+4,Z+12+4 |
Рис. 10.7. Простые макровызовы и макрорасширения
На рис. 10.7,в имена COST, PROFIT и PRICE подставляются вместо параметров A, B и C, демонстрируя тем самым, что число алфавитно-цифровых символов в имени аргумента не обязательно должно быть таким же, как в имени параметра. Ключевым моментом при этом является то, что производится подстановка символьных строк, а не адресов. Было бы неверно сказать, что адрес PRICE подставляется вместо адреса A. В действительности имя A может даже никогда не использоваться как адрес. Имеет место то, что макропроцессор подставляет вместо параметров символьные строки - имена аргументов. Полученный в результате текст обрабатывается ассемблером, как обычный текст программы на языке ассемблера.
На рис. 10.7, п.г показано, как осуществляется подстановка символьной строки. При этом символьные строки X+4, Y+8 и Z+12 подставляются вместо параметров A, B и C. Отметим, что подстановка является в чистом виде подстановкой символьной строки. Строка Х+4 подставляется вместо параметра A даже в выражении A+4. Поэтому в результате подстановки будет получено Х+4+4, а не Х+8.
По тому как эта конкретная макроинструкция была записана (см. рис. 10.6), аргументы должны быть адресами или выражениями для вычисления адресов. Другие режимы адресации, например R0, @8(AP) и X[R1], использовать нельзя, поскольку могут быть порождены выражения следующего вида, недопустимые в рассматриваемом контексте:
R0+4 @8(AP)+4 Х[R1]+4
Макроинструкция, представленная на рис. 10.8, является измененным вариантом макроинструкции рис. 10.6, который можно использовать с большинством режимов адресации.
.MACRO ADDQ3 A,B,C MOVQ B,-(SP) MOVQ A,-(SP) ADDL2 (SP)+,4(SP) ADWC (SP)+,4(SP) MOVQ (SP)+,C .ENDM ADDQ3
Рис. 10.8. Другой вариант макроинструкции, приведённой на рис. 10.6
Вследствие того что операнды инструкций помещаются для выполнения арифметических операций в стек, имена, используемые в качестве аргументов макровызова, не должны появляться в выражениях. Более того, они появляются только один раз в инструкциях пересылки квадраслова. Это важно в том случае, если подставляемый аргумент имеет вид (R5)+ или даже X[R1]. Используя при вызове макроинструкции аргументы R0, @8(AP) и X[R1], можно записать
ADDQ3 R0,@8(AP),X[R1]
В результате получено следующее макрорасширение:
MOVQ @8(AP),-(SP) MOVQ R0,-(SP) ADDL2 (SP)+,4(SP) ADWC (SP)+,4(SP) MOVQ (SP)+,X[R1]
Использование стека в этом примере является специфическим приёмом и требует тщательной проверки при адресации операндов (SP), (SP)+ и 4(SP). Читателю рекомендуется разобрать самостоятельно несколько примеров, наблюдая за содержимым стека при выполнении каждой инструкции (см. п. 5 упр. 10.2). Заметим, что данная макроинструкция не будет работать надлежащим образом, если при адресации операнда используется указатель стека SP. Тем не менее она будет работать со всеми другими режимами адресации.
Важно уяснить, что при вызове макроинструкций происходит их расширение, которое заключается в замещении каждого вхождения параметра символьной строкой соответствующего ему аргумента. Заметим, что параметры могут находиться в любом месте макроопределения и идентифицируются по имени, выделенному с помощью знаков пунктуации. На рис. 10.9 представлены макроопределение и макрорасширение, которые иллюстрируют, как производится замена параметров в различных полях макроопределения.
(а) Макроопределение |
---|
.MACRO TEST ABC,DEF,HIJ ABC: DEF HIJ INCL HIJ .ENDM TEST |
(б) Макровызов |
TEST LOOP1,CLRL,COUNT |
(в) Макрорасширение |
LOOP1: CLRL COUNT INCL COUNT |
Рис. 10.9. Макроинструкция с замещаемыми параметрами
Заметим, во-первых, что строки текста в макроопределении сами по себе не являются корректным исходным текстом программы на ассемблере. В первой строке в поле кода операции содержится код DEF, инструкция с таким кодом отсутствует в ЭВМ семейства VAX. Однако DEF представляет собой замещаемый параметр и в макрорасширении не появляется, а замещается на код CLRL, который является допустимым кодом операции ЭВМ семейства VAX.
Другой важный момент, который демонстрируется в этом примере, заключается в том, что допускается подстановка меток. Действительно, почти всегда имеется необходимость в том, чтобы метки, присутствующие в макроопределении, были замещаемыми параметрами. Предположим, что метка ABC не является замещаемым параметром. Тогда если вызов макроинструкции производится два или более раз, то имя ABC появится в качестве метки более одного раза, что приведёт к ошибке повторного определения.
Часто, в особенности в больших программах, потребность в появлении некоторых фрагментов текста программы возникает не всегда, а только при определённых условиях. Примерами этого являются:
Ненужную часть текста для экономии памяти можно удалить из программы. Однако это не всегда лёгкая задача. Редактирование большой программы в лучшем случае - рискованное занятие. При этом всегда имеется опасность либо удалить лишнее, либо не то, что нужно. К тому же всегда бывает очень трудно восстановить первоначальный вариант, если вы отказались от изменения программы. Все эти проблемы можно решить с помощью условного ассемблирования.
Условное ассемблирование позволяет включить или проигнорировать при ассемблировании некоторый идентифицированный фрагмент текста программы. Такой фрагмент текста называется блоком условного ассемблирования и ограничивается двумя директивами ассемблера. Начало блока отмечается директивой .IF, а конец - директивой .ENDC. Директива .IF содержит описание логического условия. Если условие выполняется, фрагмент текста, находящийся в блоке условного ассемблирования, включается ассемблером в программу. В противном случае весь блок пропускается так, будто его не существовало.
На рис. 10.10 показан простой пример блока условного ассемблирования. Значение, соответствующее имени TEST, определяет, будет или нет ассемблироваться данный блок. Если значение TEST не равно 0, то выполняется ассемблирование блока, в противном случае блок будет пропущен.
.IF NOT_EQUAL,TEST MOVL X,Y XCODE: INCL R0 ADDL2 W,R1 .ENDC
Рис. 10.10. Блок условного ассемблирования
Следует особо отметить тот факт, что в блоке содержится определение метки XCODE. Следовательно, если этот блок будет пропущен, то ссылки на имя XCODE приведут к выдаче сообщения об ошибке появления неопределённого имени. Чтобы избежать этого, необходимо:
Заметим также, что в данном блоке условного ассемблирования будут порождаться инструкции, что влияет на счётчик адреса. Поэтому имя TEST должно быть определено в программе заранее. Обычно имена, которые участвуют в управлении блоками условного ассемблирования, определяются с помощью знака равенства = в начале программы, где их легко обнаружить, если потребуется внести изменения.
На рис. 10.10 используется операция отношения NOT_EQUAL для проверки условия не равно. Как и следовало ожидать, можно использовать все шесть арифметических операций отношения: EQUAL, NOT_EQUAL, LESS_THEN, LESS_EQUAL, GREATER_EQUALL и GREATER (для операций отношения равно, не равно, меньше, меньше или равно, больше или равно и больше). Для всех этих отношений выполняется сравнение значения, заданного как аргумент выражения, с нулём. Синтаксис директивы .IF дан ниже:
.IF ОТНОШЕНИЕ,ВЫРАЖЕНИЕ
Например, директива
.IF GREATER_EQUALL,Х-5
приведёт к пропуску фрагмента текста программы, если только значение выражения X-5 не будет больше или равно 0. Иначе говоря значение X должно быть больше или равно 5 для того, чтобы выполнилось ассемблирование фрагмента текста программы, помещённого в блок условного ассемблирования.
Дополнительно к описанным выше арифметическим условиям существует шесть символических условий:
.MACRO NULL X,Y,Z .IF BLANK,Y .LONG X,Z .ENDC .ENDM NULL
При макровызове NULL 5,ABC,6 не происходит порождения кода, тогда как при макровызове NULL 5, ,6 будет образовано два длинных слова. Заметим, что если в директиве .LONG имеется более одного аргумента, то происходит генерация нескольких длинных слов.
Очевидно, четыре последние директивы условного ассемблирования предназначены для использования в макроопределениях. Действительно, условное ассемблирование в основном используется в макроинструкциях. Условное ассемблирование может быть использовано для управления генерацией строк текста программы, которые ассемблируются в зависимости от значений аргументов или даже от числа обращений к макроинструкции. Например, рассмотрим макроинструкцию, предназначенную для генерации вызывающей последовательности для подпрограммы с именем SUBR. Подпрограмма имеет три аргумента, которые передаются в списке аргументов согласно стандартной вызывающей последовательности операционной системы VAX/VMS, которая была описана в предыдущей главе. Второй и третий аргументы в подпрограмме являются необязательными. Тем не менее подпрограмме SUBR всё же нужен список аргументов, занимающий три длинных слова. Если аргумент передаётся в явном виде, то его адрес должен быть внесён в этот список; если аргумент опущен, то в список должен быть помещён фиктивный нулевой адрес. На рис. 10.11 показано, как можно написать такую макроинструкцию.
Заметим, что в этой макроинструкции проверяется, не пропущены ли аргументы, соответствующие параметрам B и C. Если это так, то в список аргументов заносится нулевое значение. В противном случае в стек помещается соответствующий адрес. Заметим, что первое должно быть сделано, при выполнении условия BLANK, второе - при выполнении условия NOT_BLANK, что очень похоже на структуру IF-THEN-ELSE, используемую в языках высокого уровня. В действительности в ассемблере ЭВМ семейства VAX имеется аналог оператора ELSE для условного ассемблирования - это директива .IF_FALSE. Данная директива вызывает ассемблирование расположенного за ней фрагмента текста, если предыдущий фрагмент был пропущен, или вызывает пропуск фрагмента, если предыдущий фрагмент был ассемблирован. Имеется ещё две похожие директивы условного ассемблирования: директива .IF_TRUE, которая ставит условное ассемблирование снова в зависимость от первоначального условия, и директива .IF_TRUE_FALSE, которая приводит к тому, что ассемблирование производится независимо от условия. На рис. 10.12 показано, каким образом можно переписать макроинструкцию, представленную на рис. 10.11, более кратко с использованием директивы .IF_FALSE; в результате отпадает необходимость в анализе противоположного условия.
.MACRO DO_SUBR A,B,C .EXTERNAL SUBR .IF NOT_BLANK,C PUSHAL C ;ПОМЕСТИТЬ В СТЕК АДРЕС C .ENDC .IF BLANK,C PUSHL #0 ;ИЛИ 0 .ENDC .IF NOT_BLANK,B PUSHAL B ;ПОМЕСТИТЬ В СТЕК АДРЕС В .ENDC .IF BLANK,B PUSHL #0 ;ИЛИ 0 .ENDC PUSHAL A ;ПОМЕСТИТЬ В СТЕК ТРЕБУЕМЫЙ АДРЕС А CALLS #3,SUBR ;И ВЫЗВАТЬ ПОДПРОГРАММУ .ENDM DO_SUBR
Рис. 10.11. Макроинструкция для вызова подпрограммы
.MACRO DO_SUBR A,B,C .EXTERNAL SUBR .IF NOT_BLANK,C PUSHAL C ;ПОМЕСТИТЬ В СТЕК АДРЕС C .ENDC .IF_FALSE PUSHL #0 ;ИЛИ 0 .ENDC .IF NOT_BLANK,B PUSHAL B ;ПОМЕСТИТЬ В СТЕК АДРЕС В .ENDC .IF_FALSE PUSHL #0 ;ИЛИ 0 .ENDC PUSHAL A ;ПОМЕСТИТЬ В СТЕК ТРЕБУЕМЫЙ АДРЕС А CALLS #3,SUBR ;И ВЫЗВАТЬ ПОДПРОГРАММУ .ENDM DO_SUBR
Рис. 10.12. Другой вариант макроинструкции для вызова подпрограммы
Не вдаваясь в подробности, отметим, что блоки условного ассемблирования могут быть вложены в другие блоки условного ассемблирования, а внутри макроинструкции могут появляться макроопределения. Директивы .IF - .ENDC и .MACRO - .ENDM подобно скобкам должны использоваться парами. Такое объединение в пары позволяет создавать сложные структуры для построения комбинированных условий и для написания макроинструкций, в которых после их вызова определяются другие макроинструкции. Помимо этого использование имени макроинструкции в директиве .ENDM позволяет сделать структуру вложений более ясной.
Отметим также, что в макрорасширении может происходить вызов других макроинструкций. Это не создаёт никаких трудностей, поскольку после расширения макроинструкции управление передаётся собственно ассемблеру, который допускает появление дополнительных макровызовов. В результате будет создан дополнительный код, который просто добавится к уже имеющемуся.
Интересно, что при этом макроинструкция может вызывать сама себя. Это приводит к образованию цикла особого рода, в котором последовательно повторяется расширение макроинструкции. Однако, как и в обычных программных циклах, должен быть способ завершения цикла. Таким образом, если макроинструкции осуществляет вызов самой себя, то этот вызов должен находиться внутри блока условного ассемблирования, который в конце концов будет пропущен. Макроинструкции такого вида называются рекурсивными макроинструкциями, поскольку их свойства во многом аналогичны свойствам рекурсивных подпрограмм, описанных в гл. 9.
; ; МАКРОИНСТРУКЦИЯ ДЛЯ ГЕНЕРАЦИИ ТАБЛИЦЫ ПОСЛЕДОВАТЕЛЬНЫХ ЧИСЕЛ ; .MACRO TABLE N TABK=1 ;ИНИЦИАЛИЗАЦИЯ TABK TAB N ;НАЧАЛО ОБРАБОТКИ .ENDM TABLE ; ; РЕКУРСИВНАЯ МАКРОИНСТРУКЦИЯ, ИСПОЛЬЗУЕМАЯ В TABLE ; .MACRO TAB N .IF LESS_EQUAL,TABK-<N> ;ЗАВЕРШИТЬ ГЕНЕРАЦИЮ ЧИСЕЛ? .LONG TABK ;НЕТ, ОБРАЗОВАТЬ НОВОЕ ЧИСЛО TABK=TABK+1 TAB N ;РЕКУРСИВНОЕ ОБРАЩЕНИЕ .ENDC TABLE .ENDM TAB
Рис. 10.13. Рекурсивная макроинструкция
На рис. 10.13 представлена рекурсивная макроинструкция, предназначенная для генерации таблицы чисел от 1 до N, генерация таблицы осуществляется с помощью макровызова TABLE N. Заметим, что макроинструкция TABLE обращается к макроинструкции TAB, которая является рекурсивной. В строке с директивой .IF число N заключено в скобки, поскольку вместо него может быть подставлено выражение.
.MACRO ORD A,B MOVL A,B CLRL A SUBL B,A .ENDM ORD .MACRO SPEC A,B,C MOVL #A,АА B A,C CLRB C+6 .ENDM SPEC
Покажите, как будут выглядеть макрорасширения, полученные в результате следующих макровызовов:
а. | ORD |
SUM,TOTAL |
б. | ORD |
R0,(R1)+ |
в. | ORD |
A[R0],B |
г. | ORD |
B,A |
д. | SPEC |
XMAX,MOVL,W |
e. | SPEC |
1000,ADDL2,C+6 |
ж. | SPEC |
X,ORD,1000 |
3. | SPEC |
B,ORD,A |
a. |
.IF EQUAL,A-3 MOVL X,Y .ENDC |
б. |
.IF NOT_EQUAL,A-3 MOVL #3,W .ENDC |
в. |
.IF GREATER,B MOVL R,R0 .ENDC |
г. |
C=0 .IF LESS_THEN,B-4 C=1 .ENDC .IF EQUAL,C MOVL H,Q .ENDC |
д. |
C=0 .IF EQUAL,B C=1 .IF EQUAL,C MOVL U,V .ENDC MOVL L,M .ENDC |
e. |
C=0 .IF NOT_EQUAL,B C=1 .IF EQUAL,C-1 MOVL I,J .ENDC MOVL G,F .ENDC |
.MACRO ADDQ3 A,B,C MOVQ B,-(SP) MOVQ A,-(SP) ADDL2 (SP)+,4(SP) ADWC (SP)+,4(SP) MOVQ (SP)+,C .ENDM ADDQ3
Напишите расширение для макровызова
ADDQ3 X,Y,Z
Последовательно разберите инструкции, сгенерированные в макрорасширении, в том порядке, в каком они будут следовать на стадии выполнения. Покажите, как меняется содержимое квадраслов X, Y и Z, а также стека при выполнении каждой инструкции.
LDA |
M |
Загрузить содержимое ячейки памяти M в сумматор |
STA |
M |
Занести содержимое сумматора в ячейку памяти M |
ADDA |
M |
Сложить содержимое ячейки M с содержимым сумматора |
SUBA |
M |
Вычесть содержимое ячейки M из содержимого сумматора |
JUMP |
M |
Перейти по адресу M |
JMI |
M |
Перейти по адресу M, если содержимое сумматора отрицательно |
JZ |
M |
Перейти по адресу M, если содержимое сумматора равно 0 |
READ |
|
Ввести числа и загрузить в сумматор |
|
Напечатать содержимое сумматора |
|
STOP |
|
Останов |
Следующая программа распечатывает первые десять степеней числа два, вычисляя выражение 2Х как X+X:
.TITLE ПРОГРАММА ГИПОТЕТИЧЕСКОЙ ВЫЧИСЛИТЕЛЬНОЙ МАШИНЫ START: LDA MTEN ; ИНИЦИАЛИЗИРОВАТЬ СЧЁТЧИК STA COUNT ; ЗНАЧЕНИЕМ -10 LDA ONE ; ИНИЦИАЛИЗИРОВАТЬ POWER STA POWER LOOP: LDA POWER ; УМНОЖИТЬ POWER HA 2 ADDA POWER STA POWER PRINT ; ПЕЧАТЬ POWER LDA COUNT ; ИЗМЕНИТЬ СОДЕРЖИМОЕ СЧЁТЧИКА ADDA ONE STA COUNT JMI LOOP ; ПОКА ОТРИЦАТЕЛЬНОЕ, ПОВТОРЯТЬ В ЦИКЛЕ STOP ; ЗАВЕРШИТЬ ВЫПОЛНЕНИЕ MTEN: .LONG -10 ; НАЧАЛЬНОЕ ЗНАЧЕНИЕ СЧЁТЧИКА ONE: .LONG 1 ; КОНСТАНТА ONE COUNT: .BLKL 1 ; ОБЛАСТЬ ПЕРЕМЕННЫХ POWER: .BLKL 1 ; ОБЛАСТЬ ПЕРЕМЕННЫХ .END START
Напишите набор макроинструкций, которые имитировали бы работу такой гипотетической машины, заменяя каждую инструкцию инструкциями ЭВМ VAX с эквивалентным действием. Проверьте действие макроинструкций на приведённом примере программы.
В ассемблере ЭВМ семейства VAX используются два способа идентификации аргумента в макровызове. Более простым является тот, который уже был рассмотрен. При этом способе аргумент идентифицируется по его позиции в макровызове. Параметры, обеспечивающие передачу аргументов таким образом, называются позиционными.
Например, в следующих макроопределении и макровызове:
.MACRO MAC A,B,C ... .ENDM MAC ... MAC XXX,YYY,ZZZ
произойдёт подстановка аргумента XXX вместо параметра A, YYY - вместо B, a ZZZ - вместо C. То, как устанавливается соответствие, зависит от порядка следования аргументов, указанных в макровызове.
Другой способ называется передачей аргументов с помощью ключевых параметров. B этом случае соответствие параметров, используемых в исходном макроопределении, аргументам макровызова устанавливается с помощью знака равенства. Например, для предыдущего примера следующий вызов:
MAC B=XXX,C=YYY,A=ZZZ
снова приведёт к подстановке аргумента XXX вместо параметра A, YYY - вместо B и ZZZ - вместо C. Поскольку подстановка идентифицируется с помощью имени или ключевого слова, очерёдность указания аргументов не имеет значения и на самом деле в данном примере порядок следования аргументов намеренно нарушен. Цена, которую приходится платить за возможность нарушать очерёдность, такова, что приходится вводить с терминала больше символов. Но часто это даёт такие преимущества, которые перевешивают необходимость набора дополнительных символов.
Основная причина использования макровызовов такого типа заключается в том, что сложные макроинструкции, подобные системным макроинструкциям, описанным в гл. 11, часто имеют большое количество параметров и трудно запомнить очерёдность, в которой они были специфицированы в исходном макроопределении. Это усугубляется тем, что программное обеспечение часто обновляется, в результате чего возникает необходимость в модификации таких макроинструкций. Другая причина заключается в том, что очень часто многие из параметров сложной макроинструкции являются необязательными, и их нужно задавать только в особых случаях. В результате при позиционной форме записи потребовалось бы большое количество запятых для обозначения позиции неиспользуемых или опущенных аргументов. Для примера рассмотрим макроинструкцию с именем GENERATE, в которой имеется 15 замещаемых параметров. Если в конкретном макровызове задаются только два аргумента, 6-й и 13-й, то при позиционном способе записи макровызов имел бы вид
GENERATE ,,,,,FTAB,,,,,,,XLOC
Заметим, что поставлено меньше 14 запятых, так как XLOC подставляется вместо 13-го параметра, а поскольку ничего не нужно подставлять вместо 14-го и 15-го параметров, то последние запятые не нужны. Однако если 6-му параметру было присвоено имя FUNC, а 13-му - VALUE, то макровызов с явным указанием имён параметров можно было бы осуществить следующим образом:
GENERATE FUNC=FTAB,VALUE=XLOC
При таком способе записи нет необходимости знать, сколько параметров было в исходном макроопределении и в каком порядке они были записаны. А использование в качестве ключевых слов мнемонических обозначений типа FUNC и VALUE позволяет сделать программу более понятной.
Поэтому в руководствах для системного программиста операционной системы VAX/VMS обычно предпочитают определять системные макроинструкции с использованием ключевых, а не позиционных параметров. Другие примеры того, как это делается, приведены в следующей главе, в которой описываются системные макроинструкции ввода-вывода.
Как видно из предыдущего раздела, не столь уж редки случаи, в особенности для системных макроинструкций, когда в макровызовах пропущен аргумент. Обычно пропуск аргумента приводит к тому, что вместо соответствующего ему параметра не происходит никакой подстановки. Имя параметра уничтожается, и на место ничего не помещается. Например, рассмотрим макроопределение
.MACRO ADDX A,B,C ADDL3 A,B,C .ENDM ADDX
Если макровызов имеет вид
ADDX SUM,,TOTAL
то расширением будет
ADDL3 SUM,,TOTAL
что является ошибочным (конечно, если только не было произведено переопределение имени ADDL3 в качестве макроинструкции). В некоторых случаях может потребоваться, чтобы макроинструкция, подобная этой, при пропуске аргумента приобретала особый смысл. Например, такую макроинструкцию можно определить таким образом, чтобы в случае пропуска среднего аргумента она порождала бы инструкцию, которая прибавляет к первому аргументу 1. Этого можно достичь с помощью условного ассемблирования с использованием директивы .IF BLANK,... Однако при этом обычно требуется добавить в макроопределение небольшое количество дополнительных строк. Например, предыдущее макроопределение примет вид
.MACRO ADDX A,B,C .IF BLANK,B ADDL3 A,#1,C .IF_FALSE ADDL3 A,B,C .ENDC .ENDM ADDX
Очевидно, что использование этого приёма для сложной макроинструкции с несколькими параметрами, имеющими особый смысл в случае пропуска аргументов, может потребовать очень большой объём дополнительного кода. Если для каждого пропущенного аргумента удваивается объём кода, то может потребоваться 215 строк текста программы.
Существует более простой способ - подстановка значения параметра по умолчанию. При такой подстановке для каждого параметра заранее задаётся строка символов. Если в макровызове аргумент опущен, то вместо него по умолчанию используется эта строка. Строки, подставляемые по умолчанию, помещаются в список параметров макроопределения и помечаются знаком равенства. Чтобы продемонстрировать этот способ, снова рассмотрим макроинструкцию ADDX, которую можно переопределить следующим образом:
.MACRO ADDX A,B=#1,C ADDL3 A,B,C .ENDM ADDX
В данном случае параметру B для подстановки по умолчанию назначена строка #1. Поэтому для макровызова
ADDX SUM,,TOTAL
будет порождено макрорасширение
ADDL3 SUM,#1,TOTAL
Однако если аргумент, соответствующий параметру B, указан, то подстановка происходит обычным способом, так что макровызов
ADDX SUM,FIVE,TOTAL
порождает макрорасширение
ADDL3 SUM,FIVE,TOTAL
В этом подразделе описывается другой вид подстановки по умолчанию в случае пропущенных или пустых аргументов. Напомним, что выше в этой главе рекомендовалось соблюдать осторожность при использовании меток внутри макроопределения. Причина этого заключается в том, что, если обращение к макроинструкции производится в программе несколько раз, обращение к такой метке вызывает ошибку повторного определения. Одно из решений проблемы - сделать так, чтобы метка стала замещаемым параметром. В этом случае программист при вызове макроинструкции всякий раз мог бы назначать для метки своё собственное уникальное символическое имя. Например, рассмотрим следующую макроинструкцию, производящую очистку массива из N длинных слов:
.MACRO CLEAR A,N,L CLRL R0 L: CLRL A[R0] AOBLSS N,R0,L .ENDM CLEAR
В случае макровызова
CLEAR ARRAY,#20,30$
макрорасширение примет вид
CLRL R0 30$: CLRL ARRAY[R0] AOBLSS #20,R0,30$
Этот приём действует безотказно. Однако при этом требуется, чтобы программист постоянно заботился о таких элементах макроинструкции, которые сами по себе несущественны с точки зрения общего назначения макроинструкции, а именно о подборе уникального имени для метки L. При выборе подходящего для этой цели аргумента нужно соблюдать осторожность. В данном случае, например, важно, чтобы символическое имя 30$ не использовалось бы в качестве метки в другом месте данного блока локальных имён.
Решение этой проблемы заключается в том, чтобы возложить на ассемблер функцию генерации локальных имён. Этого можно добиться, помещая знак вопроса перед именем параметра в строке макроопределения. В частности, вышеупомянутая макроинструкция может быть переопределена следующим образом:
.MACRO CLEAR A,N,?L CLRL R0 L: CLRL A[R0] AOBLSS N,R0,L .ENDM CLEAR
Если параметр помечен знаком вопроса, а в макровызове опускается соответствующий аргумент, то ассемблером будет сгенерировано собственное локальное имя. Все генерируемые локальные имена записываются в виде большого числа (не менее 30000), после которого следует знак доллара. Ассемблер начинает с числа 30000 и использует последовательно числа 30001, 30002, ... каждый раз, когда требуется сгенерировать новое имя. Например, макровызов
CLEAR ARRAY,#20
порождает макрорасширение
CLRL R0 30015$: CLRL ARRAY[R0] AOBLSS #20,R0,30015$
Из этого следует, что перед этим было сгенерировано 15 локальных имён и таким образом счёт был доведён до 30015.
Разумеется, могут быть случаи, когда программисту потребуется получить доступ к метке, находящейся вне макроинструкции. Тогда задание имени метки в явном виде подавляет генерацию локальных имён. Так, для макровызова
CLEAR ARRAY,#20,30$
макрорасширение будет таким же, как и в предыдущем случае, однако вместо L будет подставлена метка 30$ и не произойдёт генерации локального имени.
Обычно имена, используемые для замещаемых параметров в макроинструкциях, идентифицируются по отделяющим их пробелам или с помощью специальной пунктуации. Так, например, в макроинструкции
.MACRO EVER A,B MOVB A,B .ENDM EVER
замена буквы B в слове MOVB производиться не будет, а параметр B, указанный в поле операндов этой инструкции, будет замещён.
В большинстве случаев желаемый результат может быть получен и таким способом, но иногда бывает полезным способ создания символических имён путём конкатенации, или объединения символьных строк. Этот способ реализуется в макроинструкциях с использованием апострофа, который выполняет в макрорасширениях особую функцию. Апостроф является знаком пунктуации и, следовательно, ограничителем для имени параметра. Однако в макрорасширении апострофы, примыкающие к имени замещаемого параметра, уничтожаются. Следующая макроинструкция поясняет использование апострофа:
.MACRO SLIDE A,B,X MOV'X A,B .ENDM SLIDE
Апостроф в символьной строке MOV’X - ограничитель для X, который является замещаемым параметром, и, поскольку апостроф примыкает к замещаемому параметру, он будет уничтожен. Это означает, что в результате макровызова
SLIDE #5,DATA,L
будет получено макрорасширение
MOVL #5,DATA
Иногда желательно сохранить апостроф в макрорасширении. Это возможно, поскольку апостроф, не примыкающий к замещаемому параметру, в макрорасширении сохраняется. При этом иногда может потребоваться использовать в макроопределении два или три апострофа, чтобы в результате был сохранен один. Например, макроопределение и макровызов
.MACRO STRING X,Y .ASCII /X'''Y/ .ENDM STRING STRING DON,T
порождают макрорасширение
.ASCII /DON'T/
Использование апострофов продиктовано желанием исключить лишние знаки пунктуации. Иногда возникает обратная задача. В макроинструкцию требуется передать строку-аргумент, содержащую знаки пунктуации, например запятые. Это вызывает определённые трудности, поскольку символьная строка A,B воспринимается как два фактических параметра, а не как один. Решением является использование угловых скобок < и >, в которые заключается аргумент в макровызове; при этом угловые скобки удаляются, а любая строка, находящаяся внутри скобок, используется в качестве строки подстановки макроинструкции. Строка может содержать пробелы, запятые и даже парные угловые скобки. В качестве примера использования этого приёма рассмотрим макроинструкцию
.MACRO WHAT A,B A B .ENDM WHAT
Если её вызов осуществляется следующим образом:
WHAT MOVL,<SRC,DEST>
то макрорасширение будет иметь вид
MOVL SRC,DEST
Заметим, что один уровень вложенности скобок убирается, но если внутри имеются другие скобки, они сохраняются. Это можно продемонстрировать на примере макровызова
WHAT WHAT,<ADDL3, <R0, #2, R5>>
Расширением для него будет
WHAT ADDL3,<R0,#2,R5>
что, в свою очередь, приведёт к такому расширению:
ADDL3 R0,#2,R5
Имеется возможность назначения альтернативных ограничителей, чтобы в результате в строке-аргументе можно было использовать непарные угловые скобки. (Подробности см. в руководстве "VAX/VMS Macro Language Reference Manual".)
Как следует из предыдущего раздела, макроопределения и макровызовы могут содержать большие объёмы информации. Может оказаться, что в строке не хватает места для размещения всех параметров, значений, принимаемых по умолчанию, либо аргументов в макроопределениях или макровызовах. Решением этой проблемы является использование строк продолжения, которые выполняют те же самые функции, что и строчки продолжения в программах на Фортране. Способ формирования строчек продолжения в языке ассемблера ЭВМ семейства VAX сводится к тому, что если последний аргумент, указанный в строке, состоит только из знака "минус", то следующая строка рассматривается в качестве строки продолжения. Допускается наличие нескольких строк продолжения, каждая из которых имеет в конце знак "минус". Знаки "минус" при конечной интерпретации строки кода уничтожаются. Например, макровызов
DOMAC A=ARG1,B=ARG2,C=ARG3,D=ARG4
может быть записан в виде
DOMAC A=ARG1,- B=ARG2,- C=ARG3,- D=ARG4
Точка с запятой, которая используется перед комментарием, действует как маркер конца строки. Поэтому комментарии могут быть записаны в любой из строк продолжения. Например, предыдущий макровызов может быть переписан в следующем виде:
DOMAC A=ARG1,- ;ПЕРВЫЙ АРГУМЕНТ B=ARG2,- ;ВТОРОЙ АРГУМЕНТ C=ARG3,- ;И Т.Д. D=ARG4
.MACRO PROB EXP,TOTAL,SUM,МАХ
Покажите, как можно переписать каждый из последующих макровызовов, используя способ передачи аргументов с помощью ключевых параметров вместо способа передачи аргументов с помощью позиционных параметров:
а. | PROB A,B,C,D |
б. | PROB A+5,B(R5)[R3],-(SP),#27 |
в. | PROB A,,,D |
г. | PROB ,R5,<A,B>,LONGSYMBOL |
д. | PROB ,,(SР)+ |
е. | PROB <A,B,C,D> |
.MACRO SECOND CHAN,ADS,ORK,W
Преобразовать следующие макровызовы с ключевыми параметрами в эквивалентные макровызовы с позиционными параметрами:
а. | SECOND CHAN=A,ADS=B,ORK=C,W=D |
б. | SECOND ADS=A,ORK=B,CHAN=C,W=D |
в. | SECOND ADS=ORK,ORK=W,CHAN=ADS,W=CHAN |
г. | SECOND ADS=(R5)+,W=-(SP) |
д. | SECOND CHAN=<A,B> |
e. | SECOND W=<A,B> |
.MACRO TEST ARRAY=BLOCK,SIZE=#100,ANS=R0,?LABEL CLRB ANS PUSHR #^M<R1,R2> MOVAB ARRAY,R1 CLRL R2 LABEL: XORB (R1)+,ANS AOBLSS SIZE,R2,LABEL POPR #^M<R1,R2> .ENDM TEST
Покажите, как будут выглядеть макрорасширения для следующих макровызовов, если в качестве генерируемого локального имени будет использоваться 30000$:
а. | TEST ARRAY=LIST |
б. | TEST SIZE=#25 |
в. | TEST ARRAY=BLOCK, SIZE=SIZE |
г. | TEST |
д. | TEST A,B,C,D |
e. | TEST LABEL=X,ANS=CSUM |
.MACRO WHATEVER A A .ENDM WHATEVER
Покажите, как будет выглядеть окончательное расширение для последующих макровызовов (регистрируйте все промежуточные шаги):
а. | WHATEVER HALT |
б. | WHATEVER <ADDL3 A,B,C> |
в. | WHATEVER <3$: ADDL2 1,R0> |
г. | WHATEVER |
д. | WHATEVER WHATEVER |
e. | WHATEVER <WHATEVER <CLRL R0>> |
A X B #100 C (R1)+ D #0 ANS R0
< НАЗАД | ОГЛАВЛЕНИЕ | ВПЕРЁД > |
[1] Единственным ограничением является то, что может произойти переполнение рабочей области памяти, используемой ассемблером.
[2] В ЭВМ семейства VAX имеется ряд инструкций, предназначенных специально для обработки данных в связных списках. Однако они довольно сложны и не относятся к рассматриваемой тематике. Подробности использования см. в гл. 13 и в руководстве "VAX Architecture Handbook".
[3] Выражения, не подчиняющиеся этим простым правилам или включающие глобальные имена, помечаются ассемблером ЭВМ семейства VAX как сложные. Сложные выражения вычисляются компоновщиком.