ГЛАВА 10. МАКРОИНСТРУКЦИИ И УСЛОВНОЕ АССЕМБЛИРОВАНИЕ

10.1. ВВЕДЕНИЕ

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

В этой главе читателю будут даны основы написания макроинструкций и использования условного ассемблирования. Однако эти темы весьма сложные и материал данной главы является только начальным их изложением. В гл. 11 будет показано, что имеется большое число макроинструкций, написанных для того, чтобы помочь программистам в использовании системных сервисных средств (system services) операционной системы VAX/VMS. Системные сервисные средства представляют собой программы и подпрограммы, позволяющие пользователю выполнять системно-ориентированные функции, например ввод и вывод. Макроинструкции, позволяющие обращаться к системным сервисным средствам, или системные макроинструкции содержатся в библиотеке, доступной для программистов, работающих с языком ассемблера. Чаще всего эти макроинструкции применяются для генерации вызывающих последовательностей и списков аргументов системных сервисных программ. Одной из таких макроинструкций, уже использованной ранее в тексте книги, является $EXIT_S, с помощью которой формируется вызов подпрограммы SYS$EXIT.

10.2. ПОВТОРЯЮЩИЕСЯ ФРАГМЕНТЫ ПРОГРАММЫ

ПРЕИМУЩЕСТВО ПОВТОРЕНИЯ ФРАГМЕНТОВ ПРИ АССЕМБЛИРОВАНИИ

При программировании задачи на языке ассемблера часто обнаруживается, что большие фрагменты программы неоднократно повторяются. Первое, что при этом приходит в голову, - избавиться от повторений, применяя циклы и подпрограммы. Хотя в принципе это верно, есть ситуации, когда циклы и подпрограммы - не лучшее решение. Ниже перечислены некоторые возможные соображения, по которым иногда нежелательно использование циклов и подпрограмм.

  1. Программы, содержащие циклы и подпрограммы, будут выполняться медленнее, чем программы с эквивалентными повторяющимися фрагментами кода. Это происходит потому, что при организации циклов требуются дополнительные инструкции для подсчёта числа повторений, индексации, проверки условий завершения цикла и передачи управления на начало цикла. Аналогично при использовании подпрограмм должен осуществляться их вызов, а затем передача управления обратно; подпрограмме должны передаваться аргументы, а также должно выполняться сохранение содержимого регистров. По этим причинам при критичном времени выполнения программы может возникнуть необходимость в том, чтобы отказаться от применения циклов и подпрограмм (особенно на самых нижних уровнях вложенности программы).
  2. В некоторых случаях требуется повторение фрагмента, но оно не должно быть точным. Это возможно при применении подпрограммы со сложной структурой аргументов, однако часто такое решение неприемлемо. Если в подпрограмме выполняются простейшие действия, то объём дополнительного кода, необходимого для вызова подпрограммы и передачи ей аргументов, может оказаться больше, чем объём кода, получаемого при обычном повторении фрагмента программы.
  3. Наконец, повторяемый фрагмент программы может включать данные, а не инструкции. Предположим, например, что массив должен быть целиком заполнен пятёрками. Это можно сделать с помощью программы инициализации, которая заносит число 5 во все элементы массива. Но иногда неудобно использовать программы инициализации и более предпочтительной является инициализация массива, осуществляемая на этапе ассемблирования. Значение 5 заносится в элементы массива при неоднократном повторении директивы .LONG 5.

Выше приведены далеко не все возможные соображения, но и они позволяют получить некоторое представление о том, почему предпочтительнее повторение фрагментов программы, чем использование циклов. Именно эти соображения явились предпосылками для создания макроассемблеров.

БЛОКИ ПОВТОРЕНИЯ

В языке ассемблера простейший тип повторения представляет блок повторения. Блок повторения - это фрагмент текста программы, повторяемый несколько раз без каких-либо изменений. Примером того, когда в этом возникает необходимость, является случай, приведённый выше, - массив, заполненный пятёрками. Его можно сформировать, поместив одну за другой несколько строчек .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.

10.3. СИМВОЛИЧЕСКИЕ ВЫРАЖЕНИЯ

ОСОБЕННОСТИ ФОРМИРОВАНИЯ ВЫРАЖЕНИЙ

В предшествующих главах использовались символические выражения простейшего вида. Обычно такие выражения сводились к выражениям вида 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].

  1. В качестве значения выражения должно быть получено нечто такое, что можно поместить в таблицу имён, т.е. в результате вычисления должно получиться следующее:
    • а)  число или абсолютный адрес;
    • б)  адрес ячейки в программе или адрес ячейки, имеющей фиксированное смещение от заданной ячейки программы, например ячейки, смещённой на 100 байтов после конца вашей программы;
    • в)  глобальный адрес плюс или минус фиксированное число.
  2. Выражение, полностью состоящее из чисел и абсолютных имён будет абсолютным.
  3. Переместимое имя плюс или минус абсолютное значение будет переместимым выражением. Это объясняется тем, что ячейка, смещённая на фиксированное расстояние от некоторой ячейки в программе, при перемещении программы будет также перемещаться (и поэтому является переместимой).
  4. Разность между адресами двух переместимых имён, определённых в одной программной секции, является абсолютной. Это объясняется тем, что разность между двумя адресами представляет собой число ячеек, расположенных между этими двумя адресами. Если оба адреса являются переместимыми и расположены в одной программной секции, то при перемещении программной секции они изменятся на одну и ту же величину, Поэтому число ячеек между ними остаётся неизменным или фиксированным.

Приведённые выше правила могут, разумеется, применяться и к частям сложных выражений. Кроме того, существуют два правила, относящиеся к операциям умножения и деления.

  1. В случае умножения хотя бы один из операндов должен быть абсолютным. Тогда умножение может рассматриваться как многократное сложение. Например, в случае, когда имена A, B и C являются переместимыми, рассмотрим выражение <3*A>-<2*B>-C. Это выражение может быть переписано в виде A+A+A-B-B-C, что эквивалентно A-B+A-B+A-C. Заметим, что каждая разность является разностью двух переместимых адресов и является поэтому абсолютным выражением. Следовательно, все выражение будет абсолютным.
  2. В случае деления оба операнда должны быть абсолютными, Трудно вообразить, что может быть допущено иное. Тем не менее это не мешает использовать деление в сложных выражениях, включающих переместимые части. Например, если разность между двумя адресами требуется выразить в длинных словах, а не в байтах, то можно использовать выражение <A-B>/4. Заметим, что это выражение в действительности является частным от деления двух абсолютных выражений (предполагается, что A и B являются переместимыми). Отметим также, что данное выражение более предпочтительно, чем эквивалентное <A/4>-<B/4>. Хотя такое выражение на ЭВМ семейства VAX может быть вычислено правильно при условии, что значение выражения определяется компоновщиком, оно может вызывать затруднения или привести к ошибке в случае, если значение выражения определяется на этапе ассемблирования.

ПРИМЕР ИСПОЛЬЗОВАНИЯ ВЫРАЖЕНИЙ

;ПОДПРОГРАММА 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 ("Выражение не является абсолютным"). Причиной появления такого сообщения является то, что в ассемблере подразумевается, что неопределённые выражения представляют собой глобальные, или переместимые имена, которые будут определены в дальнейшем.

Кроме того, всякий раз, когда выражение используется таким образом, что его значение должно быть известно во время ассемблирования, это выражение должно не только состоять целиком из предварительно определённых имён, но и быть настолько простым, чтобы ассемблер мог вычислить его значение. Очевидно, что поэтому не могут быть использованы сложные выражения, которые должны вычисляться компоновщиком.

УПРАЖНЕНИЯ 10.1

  1. Покажите результат ассемблирования (машинный код) следующих блоков повторения. Предполагается, что загрузка производится с ячейки ^X1200.

    а.

    .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
  2. Напишите блок повторения, который генерирует массив из 100 длинных слов с меткой NUMBS. Этот массив должен быть заполнен числами от 1 до 100.
    • а)  решите задачу, используя выражения со знаком точки;
    • б)  решите задачу, используя знак переопределения =.
  3. Предположим, что в выражениях I=2, J=3 и K=5 определяются значения, присвоенные именам I, J и K в программе на ассемблере. Определите значения следующих выражений в шестнадцатеричном представлении:
    а.

    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

  4. Предположим, что A, B и C являются переместимыми именами (т.е. метками, указывающими ячейки программы). Кроме того, I, J и K являются абсолютными именами (т.е. именами, определяющими фиксированные числа). Какие из перечисленных ниже выражений являются абсолютными, переместимыми или сложными?
    а.

    A+I

    б.

    B-K

    в.

    A-B

    г.

    A+B

    д.

    A+K-B

    е.

    <A-B>+<A-C>

    ж.

    <A-K>+<A-J>

    з.

    <A+K>*<<B-C>*4>

  5. В приведённой ниже программе появится ряд ошибок из-за неправильной очерёдности определений. Укажите, какие операторы неверно расположены и какие будут отмечены в сообщении об ошибке. (Заметим, что, хотя приведённый фрагмент не является программой, поскольку отсутствуют выполняемые операторы, переупорядочивание операторов приведёт к тому, что ассемблирование пройдёт без ошибок.)
    I=3
    A:      .BLKL  K
    B:      .BLKL  D-C
    C:      .BLKL  I
    D:      .BLKL  K+I
    K=J
    J=I+5
            .END
  6. Измените порядок операторов в программе п. 5 так, чтобы её ассемблирование проходило без ошибок.

10.4. МАКРОИНСТРУКЦИИ

ПРОСТЫЕ МАКРОИНСТРУКЦИИ

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

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

Для примера заметим, что на ЭВМ семейства 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 появится в качестве метки более одного раза, что приведёт к ошибке повторного определения.

10.5. УСЛОВНОЕ АССЕМБЛИРОВАНИЕ

ОПРЕДЕЛЕНИЕ УСЛОВНОГО АССЕМБЛИРОВАНИЯ

Часто, в особенности в больших программах, потребность в появлении некоторых фрагментов текста программы возникает не всегда, а только при определённых условиях. Примерами этого являются:

Ненужную часть текста для экономии памяти можно удалить из программы. Однако это не всегда лёгкая задача. Редактирование большой программы в лучшем случае - рискованное занятие. При этом всегда имеется опасность либо удалить лишнее, либо не то, что нужно. К тому же всегда бывает очень трудно восстановить первоначальный вариант, если вы отказались от изменения программы. Все эти проблемы можно решить с помощью условного ассемблирования.

Условное ассемблирование позволяет включить или проигнорировать при ассемблировании некоторый идентифицированный фрагмент текста программы. Такой фрагмент текста называется блоком условного ассемблирования и ограничивается двумя директивами ассемблера. Начало блока отмечается директивой .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 для того, чтобы выполнилось ассемблирование фрагмента текста программы, помещённого в блок условного ассемблирования.

ДРУГИЕ КОДЫ УСЛОВНОГО АССЕМБЛИРОВАНИЯ

Дополнительно к описанным выше арифметическим условиям существует шесть символических условий:

  1. Условие DEFINED. Истинно, если указанное как аргумент имя определено. Например, директива .IF DEFINED,XYZW приводит к порождению фрагмента текста, если имя XYZW определено и содержится в таблице имён.
  2. Условие NOT_DEFINED. Противоположно условию DEFINED.
  3. Условие BLANK. Истинно, если следующий за ним параметр заменяется пробелами. Например, рассмотрим следующую макрокоманду:
    .MACRO  NULL    X,Y,Z
    .IF     BLANK,Y
    .LONG   X,Z
    .ENDC
    .ENDM   NULL

    При макровызове NULL 5,ABC,6 не происходит порождения кода, тогда как при макровызове NULL 5, ,6 будет образовано два длинных слова. Заметим, что если в директиве .LONG имеется более одного аргумента, то происходит генерация нескольких длинных слов.

  4. Условие NOT_BLANK. Это условие противоположно условию BLANK.
  5. Условие IDENTICAL. Истинно, если следующие затем два параметра после подстановки аргументов макроинструкции окажутся идентичными строками символов.
  6. Условие DIFFERENT. Противоположно условию IDENTICAL.

Очевидно, четыре последние директивы условного ассемблирования предназначены для использования в макроопределениях. Действительно, условное ассемблирование в основном используется в макроинструкциях. Условное ассемблирование может быть использовано для управления генерацией строк текста программы, которые ассемблируются в зависимости от значений аргументов или даже от числа обращений к макроинструкции. Например, рассмотрим макроинструкцию, предназначенную для генерации вызывающей последовательности для подпрограммы с именем 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. Другой вариант макроинструкции для вызова подпрограммы

10.6. ВЛОЖЕННОСТЬ И РЕКУРСИЯ

Не вдаваясь в подробности, отметим, что блоки условного ассемблирования могут быть вложены в другие блоки условного ассемблирования, а внутри макроинструкции могут появляться макроопределения. Директивы .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 заключено в скобки, поскольку вместо него может быть подставлено выражение.

УПРАЖНЕНИЯ 10.2

  1. Пусть даны макроопределения
    .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

  2. Исходя из предположения, что имена A и B были определены как A = 5 и B = 7, покажите, будут ли ассемблированы следующие фрагменты текста программы, помещённые в блоки условного ассемблирования, а если будут, то какие:

    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
  3. Напишите вариант макроинструкции ADDQ3, представленной на рис. 10.6, имеющей два параметра. Эта макроинструкция называется ADDQ2 и предназначается для сложения квадраслов; первый аргумент складывается со вторым, так же как в инструкции ADDL2 складывалось содержимое двух длинных слов. Какие режимы адресации допускаются в вашей макроинструкции? Можно ли добиться того, чтобы макроинструкция работала со всеми режимами адресации?
  4. Переписать макроинструкцию из п. 3 таким образом, чтобы она включала директивы условного ассемблирования, которые выявляли бы случай, когда первый аргумент равен #1. При этом порождаемый код может быть значительно упрощён. Например, вместо инструкции ADDL2 может быть использована инструкция INCL и не нужна будет обработка первого операнда.
  5. На рис. 10.8 было дано следующее макроопределение:
    .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, а также стека при выполнении каждой инструкции.

  6. Вообразим, что имеется простая ЭВМ с архитектурой, подобной архитектуре ЭВМ семейства VAX, память ЭВМ состоит из 32-битовых длинных слов, содержимое которых интерпретируется как числа в дополнительном коде. В этой ЭВМ имеется только один регистр общего назначения, который называется сумматором. Все операции должны выполняться с использованием сумматора, всего имеется десять инструкций, а именно:

    LDA

    M

    Загрузить содержимое ячейки памяти M в сумматор

    STA

    M

    Занести содержимое сумматора в ячейку памяти M

    ADDA

    M

    Сложить содержимое ячейки M с содержимым сумматора

    SUBA

    M

    Вычесть содержимое ячейки M из содержимого сумматора

    JUMP

    M

    Перейти по адресу M

    JMI

    M

    Перейти по адресу M, если содержимое сумматора отрицательно

    JZ

    M

    Перейти по адресу M, если содержимое сумматора равно 0

    READ

     

    Ввести числа и загрузить в сумматор

    PRINT

     

    Напечатать содержимое сумматора

    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 с эквивалентным действием. Проверьте действие макроинструкций на приведённом примере программы.

  7. Напишите программу для гипотетической машины, описанной в п. 6 упр. 10.2, которая бы считывала два числа со знаком и перемножала их между собой, получая в результате число со знаком. Можно ли написать эффективную программу, несмотря на то, что в гипотетической машине отсутствует инструкция сдвига? Проверьте работу программы с помощью макроинструкций, которые были написаны для п. 6.

10.7. БОЛЕЕ СЛОЖНЫЕ ПРИЁМЫ НАПИСАНИЯ МАКРОИНСТРУКЦИЙ

КЛЮЧЕВЫЕ ПАРАМЕТРЫ

В ассемблере ЭВМ семейства 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

Апостроф в символьной строке MOVX - ограничитель для 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

УПРАЖНЕНИЯ 10.3

  1. Пусть макроопределение начинается со строки
    .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    ,,()+

    е.

    PROB    <A,B,C,D>

  2. Пусть макроопределение начинается со строки
    .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>

  3. Пусть дано макроопределение
            .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

  4. Пусть дано макроопределение
    .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>>

  5. Напишите макроинструкцию, расширение которой содержит инструкции для сложения четырёх длинных слов A, B, C и D, а результат помещается в ANS. Макроинструкция должна иметь пять замещаемых параметров A, B, C, D и ANS. Для каждого из этих параметров предусмотрены следующие значения, принимаемые по умолчанию:
    A       X
    B       #100
    C       (R1)+
    D       #0
    ANS     R0

 

< НАЗАД ОГЛАВЛЕНИЕ ВПЕРЁД >


[1] Единственным ограничением является то, что может произойти переполнение рабочей области памяти, используемой ассемблером.

[2] В ЭВМ семейства VAX имеется ряд инструкций, предназначенных специально для обработки данных в связных списках. Однако они довольно сложны и не относятся к рассматриваемой тематике. Подробности использования см. в гл. 13 и в руководстве "VAX Architecture Handbook".

[3] Выражения, не подчиняющиеся этим простым правилам или включающие глобальные имена, помечаются ассемблером ЭВМ семейства VAX как сложные. Сложные выражения вычисляются компоновщиком.