ГЛАВА 4. АССЕМБЛЕР И ПРОЦЕСС АССЕМБЛИРОВАНИЯ

4.1. ВВЕДЕНИЕ

Программирование на машинном языке затруднительно для программиста. Например, для сложения содержимого двух длинных слов с именами TAX и TOTAL программисту следовало бы написать в машинном коде такую инструкцию, как

Операнд Операнд Код операции

E4 AF

F2 AF

С0

При этом программист должен помнить, что ^XC0 - это код операции инструкции ADDL2, a ^XAF - спецификатор операнда, определяющий режим относительной адресации с заданным в формате байта смещением. Кроме того, чтобы получить доступ к длинным словам TAX и TOTAL, программист должен вычислить их смещение. В данном примере смещение для длинного слова TAX равно ^XF2, а для длинного слова TOTAL - ^XE4. Чтобы оценить ситуацию, с которой сталкивается программист при кодировании, стоит отметить, что ЭВМ семейства VAX имеют несколько сотен различных кодов операций; к тому же обычно в программе используются несколько тысяч ячеек памяти.

Языки ассемблера намного снижают умственную нагрузку программиста, позволяя использовать вместо чисел символические имена. Например, предыдущую инструкцию на машинном языке можно написать на языке ассемблера следующим образом:

ADDL2   TAX,TOTAL

Программа, называемая ассемблером, выполняет трансляцию программы на языке ассемблера в программу на машинном языке, заменяя символические имена и специальные символы соответствующими числами. Для приведённого выше оператора языка ассемблера. Ассемблер произведёт замену мнемоники ADDL2 на код операции ^XC0, символического имени TAX на коды ^XAF и ^XF2, имени TOTAL на коды ^XAF и ^XE4. Обратите внимание, что операторы языка ассемблера читаются обычным образом - слева направо. Рассматриваемый оператор языка ассемблера следует читать как "Сложить содержимое длинного слова, начинающегося с адреса TAX, с содержимым длинного слова, начинающегося с адреса TOTAL, а результат поместить в длинное слово, начинающееся с адреса TOTAL".

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

Обычно для каждого типа (или модели) ЭВМ имеется свой собственный язык ассемблера. Но бывает и так, что иногда для ЭВМ одного типа (или модели) существуют различные языки ассемблера. Фактически для ЭВМ семейства VAX имеются по крайней мере два различных ассемблера. Одним из них является программа-ассемблер, выполняемая под управлением операционной системы UNIX. Вторым является ассемблер, называемый VAX-11 MACRO или просто MACRO - макроассемблер, поставляемый в составе ОС VMS производителем ЭВМ семейства VAX - фирмой Digital Equipment Corporation. В данной книге будет рассматриваться только ассемблер VAX-11 MACRO.

4.2. ПРИМЕР ИСПОЛЬЗОВАНИЯ ЯЗЫКА АССЕМБЛЕРА

ПРОГРАММА НА ЯЗЫКЕ АССЕМБЛЕРА

На рис. 4.1 показана программа на языке ассемблера, в которой выполняется сложение чисел 26, 38 и 5, представленных в формате байта, а полученная сумма (число 69) помещается в ячейку памяти SUM. Как показано на рисунке, операторы языка ассемблера состоят из четырёх полей. Первое поле называется полем метки и используется для определения символических адресов, таких как A, B, C, SUM и START. Вторым полем является поле кода операции, в нем может находиться мнемоническое обозначение кода операции, например MOVB, ADDB2 и RET. Кроме того, в этом поле могут помещаться директивы ассемблера, например .BYTE, .BLKB, .WORD и .END. Директивы дают указание ассемблеру выполнить некоторые специальные действия. Третье поле, называемое полем операндов, может использоваться для размещения в нём операндов инструкции. Например, в инструкции

ADDB2   B,SUM    ; SUM := SUM+B

операнды B и SUM указывают, что байт, расположенный по адресу B, должен быть сложен с байтом, расположенным по адресу SUM. Последнее поле - поле комментариев, используется для документирования программы.

Метка Код операции Операнды Примечания

A:

.BYTE

26

; БАЙТ ДАННЫХ

B:

.BYTE

38

; БАЙТ ДАННЫХ

C:

.BYTE

5

; БАЙТ ДАННЫХ

SUM:

.BLKB

1

; РЕЗЕРВИРОВАНИЕ БАЙТА

START:

.WORD

0

; В СЛОВЕ СОДЕРЖИТСЯ 0

MOVB

A,SUM

; SUM := А

ADDB2

B,SUM

; SUM := SUM+B

ADDB2

C,SUM

; SUM := SUM+C

RET

; ВОЗВРАТ

.END

START

Рис. 4.1. Программа на языке ассемблера

Машинный код Язык ассемблера
Содержимое Адрес Стр. Метка Код
операции
Операнды Примечания

0000

1

A:

.BYTE

26

; БАЙТ ДАННЫХ

26

0001

2

B:

.BYTE

38

; БАЙТ ДАННЫХ

05

0002

3

C:

.BYTE

5

; БАЙТ ДАННЫХ

00000004

0003

4

SUM:

.BLKB

1

; РЕЗЕРВИРОВАНИЕ БАЙТА

0000

0004

5

START:

.WORD

0

; В СЛОВЕ СОДЕРЖИТСЯ 0

F8 AF  F7 AF  90

0006

6

MOVB

A,SUM

; SUM := А

F3 AF  F3 AF  80

000В

7

ADDB2

B,SUM

; SUM := SUM+B

ЕЕ AF  EF AF  80

0010

8

ADDB2

C,SUM

; SUM := SUM+C

04

0015

9

RET

; ВОЗВРАТ

0016

10

.END

START

Рис. 4.2. Листинг программы на языке ассемблера

При ассемблировании ассемблер транслирует программу на языке ассемблера в программу на машинном языке. На рис. 4.2 показан фрагмент листинга программы на языке ассемблера.

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

Ниже приведена строка с номером 6 листинга программы

Машинный код Язык ассемблера
Содержимое Адрес Стр. Метка Код
операции
Операнды Примечания

F8 AF F7 AF 90

0006

6

MOVB

A,SUM

; SUM := А

Ассемблер странслировал мнемоническое обозначение кода операции MOVB в код операции ^X90 и поместил его по адресу ^X00000006, на листинге этот адрес приводится в краткой записи 0006. Адреса, приводимые в тексте книги, могут сокращаться подобным образом тогда, когда ясно, что имеется в виду. Для адресации операнда A ассемблером был выбран режим относительной адресации с заданным в формате байта смещением (спецификатор операнда ^XAF) и произведено вычисление значения этого смещения (^XF7). Для адресации операнда SUM ассемблером был использован также режим относительной адресации, смещение данного операнда равно ^XF8. Строки программы с номерами 7-9 обрабатываются аналогично, начиная со следующего доступного адреса 000В.

На листинге ассемблера строка с номером 1 имеет следующий вид:

Машинный код Язык ассемблера
Содержимое Адрес Стр. Метка Код
операции
Операнды Примечания

0000

1

A:

.BYTE

26

; БАЙТ ДАННЫХ

Директива ассемблера .BYTE применяется для резервирования памяти и задания начального значения данных, представляемых в формате байта. Если не указано противное, то ассемблер назначает адреса, начиная с адреса ^X0000. В результате метка A в рассматриваемом операторе является символическим указанием адреса ^X0000 и поэтому называется символическим адресом. По умолчанию ассемблер считает, что следующее за директивой .BYTE значение представляет собой десятичное число. Следовательно, число 26, рассматриваемое как десятичное, преобразуется ассемблером в шестнадцатеричное число ^X1A и помещается по адресу ^X0000. Строки программы с номерами 2 и 3 обрабатываются аналогично.

В четвёртой строке листинга директива .BLKB (BLock Byte - блок байтов) применяется для резервирования байта памяти без задания начального значения. Значение, следующее за директивой .BLKB, определяет число резервируемых байтов памяти. (Резервирование пространства памяти для массива из 20 байтов могло быть осуществлено с помощью директивы ассемблера .BLKB 20). В данном случае число байтов равно 1, таким образом, SUM - это просто символическое имя байта памяти, содержимое которого не определено перед началом выполнения программы. Число 00000004, расположенное в графе "Содержимое" раздела листинга "Машинный код", показывает, что следующим адресом, который может использовать ассемблер, является адрес ^X00000004. В действительности эта информация не включается в программу на машинном языке, а используется другими системными программами в процессе подготовки данной программы для выполнения. Программисту следует учитывать, что перед началом выполнения программы содержимое области памяти с адресом SUM не определено.

Директива .WORD в пятой строке программы подобна директиве .BYTE, отличаясь только тем, что по директиве .WORD резервируется память и задаётся начальное значение для данных в формате 16-битового слова. Символический адрес START является адресом точки входа в программу[1]. Операционная система VAX/VMS обращается с программами пользователей как с подпрограммами или процедурами, передавая им управление с помощью инструкций CALLS или CALLG, описанных в гл. 9. Эти инструкции требуют задания 16-битовой маски, которая обычно для основных программ равна 0. Её назначение поясняется в гл. 9. Первая выполняемая инструкция программы следует непосредственно за маской.

Директива .END в строке с номером 10 помечает физический конец программы. Символическое имя START в директиве .END определяет имя START как адрес точки входа в программу.

4.3. ПРОЦЕСС АССЕМБЛИРОВАНИЯ

ТАБЛИЦЫ ИМЁН

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

Постоянное
имя
Шестнадцатеричное
значение

ADDB2

80

ADDB3

81

ADDL2

С0

ADDL3

С1

ADDW2

А0

ADDW3

А1

Таблица имён, определяемых пользователем, содержит значения для заданных пользователем символических имён, таких как метки. В качестве примера приведём таблицу имён, определяемых пользователем, для программы рис. 4.2:

Имя, определённое
пользователем
Шестнадцатеричное
значение

A

00000000

B

00000001

C

00000002

START

00000004

SUM

00000003

В этой таблице каждому символическому имени ставится в соответствие числовое значение. В частности, A - символическое имя для адреса ^X0000, B - символическое имя для адреса ^X0001 и т.д. В листинге ассемблера таблица имён даётся в алфавитном порядке, как и в нашем примере.

ПРОСТАЯ ТРАНСЛЯЦИЯ

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

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

СОЗДАНИЕ ТАБЛИЦЫ ИМЁН

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

И наоборот, таблица имён, определяемых пользователем, специфична для каждой исходной программы, и поэтому ассемблер создаёт её для каждой программы заново. Ассемблер MACRO создаёт таблицу исходя из предположения, что числа должны размещаться в смежных байтах памяти. Используя довольно простые правила, ассемблер вычисляет число байтов, порождаемых при ассемблировании каждой строки программы. При условии, что первый сгенерированный байт должен размещаться по адресу ^X00000000, ассемблер может определить точный адрес каждой инструкции и любых данных в программе. Поскольку некоторые строки программы содержат метки, меткам ставятся в соответствие определённые адреса и таким образом формируется таблица имён, определяемых пользователем.

Более строго: ассемблер создаёт таблицу имён, определяемых пользователем, отслеживая единственное значение, а именно - адрес следующего доступного байта памяти. Ассемблер запоминает текущее значение этого адреса в длинном слове, которое называется счётчиком адресов. (Опытные программисты пишут программы по частям. Для обеспечения такого стиля работы ассемблер VAX позволяет использовать несколько счётчиков адресов, по одному для каждой программной секции.)

В счётчике адресов обычно устанавливается начальное значение ^X00000000. Ассемблер просматривает исходную программу на языке ассемблера с начала до конца, соблюдая следующие правила.

Правило 1. Если ассемблер встречает символическое имя, после которого следует двоеточие (например, START:, A: или SUM:), он считает его символическим адресом. Символическое имя вместе с текущим значением счётчика адресов заносится в таблицу имён, определяемых пользователем. Содержимое счётчика адресов не меняется.

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

Код
операции
Операнды Соответствующее
число байт

.BYTE

57

1

.WORD

0

2

.BLKB

1

1

.END

START

0

MOVB

A,SUM

ПЕРЕМЕННОЕ ДЛЯ ИНСТРУКЦИИ

ADDB2

B,SUM

ПЕРЕМЕННОЕ ДЛЯ ИНСТРУКЦИИ

RET

1

Объём памяти, занимаемый инструкциями MOVB и ADDB2, зависит от режима адресации и правил, используемых ассемблером для подбора спецификатора операнда. На рис. 4.2 для каждой из инструкций MOVB и ADDB2 требуется по 5 байтов. Применяя эти правила к программе на рис. 4.2, получаем приведённую выше таблицу имён.

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

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

На рис. 4.3 дан модифицированный вариант программы, приведённой на рис. 4.1. Он отличается от предыдущего только тем, что директивы резервирования памяти для данных A, B, C и SUM перенесены в конец программы.

Посмотрим, что произойдёт при ассемблировании этой программы. Вторая строка программы содержит оператор MOVB A,SUM. Пытаясь сгенерировать для этой строки машинный код, ассемблер может подставить числовой код операции ^X90 вместо мнемонического кода MOVB. Однако он не может вычислить значения спецификаторов операндов и смещения для операндов A и SUM, они являются символическими адресами и будут внесены в таблицу имён только при обработке шестой и седьмой строк этой программы.

Метка Код операции Операнды Примечания

START:

.WORD

0

; В СЛОВЕ СОДЕРЖИТСЯ 0

MOVB

A,SUM

; SUM := А

ADDB2

B,SUM

; SUM := SUM+B

ADDB2

C,SUM

; SUM := SUM+C

RET

; ВОЗВРАТ

A:

.BYTE

26

; БАЙТ ДАННЫХ

B:

.BYTE

38

; БАЙТ ДАННЫХ

C:

.BYTE

5

; БАЙТ ДАННЫХ

SUM:

.BLKB

1

; РЕЗЕРВИРОВАНИЕ БАЙТА

.END

START

Рис. 4.3. Модифицированная программа на языке ассемблера

Чтобы решить проблему, ассемблер делает два прохода по программе, т.е. просматривает программу на языке ассемблера дважды. Сначала не генерируется никакого машинного кода, поскольку адреса ещё не определены. После первого просмотра программы адреса уже можно определить и ассемблер может сформировать таблицу имён, определяемых пользователем. Затем, при втором проходе по программе, генерируется программа в машинном коде.

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

Но и при двух проходах остаётся проблема вычисления смещения. Например, инструкция MOVB A,SUM во второй строке данной программы может занимать от 5 до 11 байтов в зависимости от того, используется ли для адресации операндов A и SUM относительная адресация со смещением, заданным в формате байта, слова или длинного слова.

При первом проходе ассемблер должен выбрать длину инструкции, чтобы поставить в соответствие символическим адресам правильные числовые значения. Если именам A и SUM выше уже присвоены значения, то ассемблер может легко выбрать один из форматов смещения - байт, слово, длинное слово для относительной адресации A и SUM. В данной программе, однако, имена A и SUM определяются ниже и при первом проходе ассемблер не может определить, насколько далеко от инструкции MOVB будут размещаться байты с именами A и SUM.

Чтобы разрешить эту проблему, ассемблер выбирает относительную адресацию со смещением, заданным в формате длинного слова, во всех случаях, когда на первом проходе обнаруживается неопределённое символическое имя[3], Как показано на рис. 4.4, это существенно увеличивает длину программы. (Программа рис. 4.2 занимает ^X16, или 22 байта, а функционально эквивалентная программа на рис. 4.4 занимает ^X28, или 40 байтов.)

Машинный код Язык ассемблера
Содержимое Адрес Стр. Метка Код
операции
Операнды Примечания

0000 0000

1

START:

.WORD

0

; В СЛОВЕ СОДЕРЖИТСЯ 0

00000027'EF

00000024'EF

90 0002

2

MOVB

A,SUM

; SUM := А

00000027'EF

00000024'EF

80 000D

3

ADDB2

B,SUM

; SUM := SUM+B

00000027'EF

00000024'EF

80 0018

4

ADDB2

C,SUM

; SUM := SUM+C

04 0023

5

RET

; ВОЗВРАТ

1А 0024

6

A:

.BYTE

26

; БАЙТ ДАННЫХ

26 0025

7

B:

.BYTE

38

; БАЙТ ДАННЫХ

05 0026

8

C:

.BYTE

5

; БАЙТ ДАННЫХ

00000028 0027

9

SUM:

.BLKB

1

; РЕЗЕРВИРОВАНИЕ БАЙТА

0016

10

.END

START

Рис. 4.4. Листинг модифицированной программы на языке ассемблера

Чтобы избежать таких потерь, программисты, пишущие на языке ассемблера VAX, обычно размещают область данных в начале программы (см. рис. 4.1), если намереваются использовать относительную адресацию. Не следует создавать программы со структурой, аналогичной структуре программы рис. 4.3.

4.4. ЗАВЕРШЁННЫЙ ВАРИАНТ ПРОГРАММЫ НА ЯЗЫКЕ АССЕМБЛЕРА

Если программу на языке ассемблера рис. 4.1 сассемблировать, скомпоновать и выполнить, она вычислит сумму 26, 38 и 5 и в результате будет получено 69. Однако в этой программе есть недостатки, которые считаются следствием плохого стиля программирования. Улучшенный вариант этой программы показан на рис. 4.5. Первое отличие от программы рис. 4.1 заключается в использовании директивы .TITLE в начале программы. Как отмечалось в гл. 1, эта директива не требуется для выполнения, но настоятельно рекомендуется для документирования программы. Символическое имя, следующее за директивой .TITLE, в данном случае IMPROVED, рассматривается как имя программы. Остальной текст в строке (УСОВЕРШЕНСТВОВАННАЯ ПРОГРАММА НА АССЕМБЛЕРЕ) комментарий. Строки, начинающиеся с точки с запятой обрабатываются как комментарии, что показано на примере строк 2-6. Пустые строки комментариев, например строчка 5, полезно вводить в программу для деления её на разделы, что облегчает чтение программы.

Указание директивы .ENTRY в строке 13 программы - это предпочтительный способ определения точки входа в программу. Оператор .ENTRY START,0 на рис. 4.5 заменяет оператор START: .WORD 0 на рис. 4.1. Первое символическое имя (START), следующее за директивой .ENTRY, определяет адрес точки входа в программу. По этой директиве адрес START не только заносится в таблицу имён, но и помечается как некоторый специальный адрес (в данном случае - адрес точки входа, который операционная система VAX/VMS использует для передачи управления программе). Кроме того, по директиве .ENTRY генерируется 16-битовая маска, которая используется, как уже описывалось инструкциями CALLS и CALLG. Число 0, следующее за адресом START, определяет, что значение этой маски равно 0, т.е. равно значению, задаваемому ранее директивой .WORD.

Ещё одно отличие между программами рис. 4.1 и рис. 4.5 заключается в том, что инструкция RET заменена символическим именем $EXIT_S. Этот способ возврата управления операционной системе VAX/VMS наиболее предпочтителен. Имя $EXIT_S - не мнемонический код инструкции (как MOVB) и не директива (как .ENTRY), а имя макроинструкции. Макроинструкцией называется несколько строчек текста программы, которым дано некоторое имя и которые сохраняются в некотором, известном ассемблеру месте. Если в программе используется имя макроинструкции (в данном случае $EXIT_S), ассемблер автоматически заменяет его соответствующими строками текста программы. В случае макроинструкции $EXIT_S заменяющие её строки текста служат для возврата управления ОС VAX/VMS. Чтобы подчеркнуть то, что ассемблер для ЭВМ VAX включает возможности макрообработки, фирма DEC дала ему название VAX-11 MACRO. Подробно макроинструкции будут рассмотрены в гл. 10.

          Код
Метка   операции  Операнды    Примечания
        .TITLE    IMPROVED - УСОВЕРШЕНСТВОВАННАЯ ПРОГРАММА НА АССЕМБЛЕРЕ
; ОПИСАНИЕ        ПРИМЕР ВЫЧИСЛЕНИЯ СУММЫ 26+38+5
; ПРОГРАММИСТ     КЭПС И СТАФФОРД
; ДАТА            ИЮЛЬ 1,1985
;
; ОБЛАСТЬ ДАННЫХ
A:      .BYTE     26          ; БАЙТ ДАННЫХ
B:      .BYTE     38          ; БАЙТ ДАННЫХ
C:      .BYTE     5           ; БАЙТ ДАННЫХ
SUM:    .BLKB     1           ; РЕЗЕРВИРОВАНИЕ БАЙТА
;
;ОБЛАСТЬ ПРОГРАММЫ
        .ENTRY    START,0     ; АДРЕС ТОЧКИ ВХОДА
        MOVB      A,SUM       ; SUM := А
        ADDB2     B,SUM       ; SUM := SUM+B
        ADDB2     C,SUM       ; SUM := SUM+C
        $EXIT_S               ; ВОЗВРАТ
        .END      START

Рис. 4.5. Улучшенный вариант программы на языке ассемблера

4.5. СИНТАКСИС ЯЗЫКА АССЕМБЛЕРА VAX-11 MACRO

СИМВОЛИЧЕСКИЕ ИМЕНА

В ассемблере требуется, чтобы операторы языка ассемблера удовлетворяли определённым синтаксическим правилам. Часть правил касается написания символических имён, определяемых программистом, таких как A, B, C, SUM и START в программе на рис. 4.5. Символические имена могут иметь длину от 1 до 31 символа и включают буквы латинского алфавита (A-Z), цифры (0-9) и три специальных символа: знак доллара ($), точку (.) и знак подчёркивания (_). Однако символические имена не должны начинаться с цифры (0-9). Приведём примеры допустимых символических имён:

X
TAX
R2D2
THISISALONGSYMBOLICNAME
A_MORE_READABLE_LONG_NAME
JULY_4_1976
$14.96

А следующие имена недопустимы по перечисленным ниже причинам:

Недопустимое имя Причина

4_JULY_1776

ИМЯ НАЧИНАЕТСЯ С ЦИФРЫ

WAGE RATE

ИМЯ СОДЕРЖИТ НЕДОПУСТИМЫЙ СИМВОЛ - ПРОБЕЛ

GROSS-PAY

ИМЯ СОДЕРЖИТ НЕДОПУСТИМЫЙ СИМВОЛ - ЗНАК МИНУС

$1,234,56

ИМЯ СОДЕРЖИТ НЕДОПУСТИМЫЙ СИМВОЛ - ЗАПЯТУЮ

THIS_NAME_IS_LONGER_THAN_31_CHARACTERS

ДЛИНА ИМЕНИ БОЛЬШЕ ЧЕМ 31 СИМВОЛ

Выбирать имена надо с осторожностью. Употребление осмысленных имён, таких как WAGE (заработная плата), HOURS_WORKED (отработанные часы) и MONTH (месяц), сделает программу на языке ассемблера более простой для отладки и сопровождения. Следует помнить, что знак доллара не следует использовать в символических именах. В операционной системе VAX/VMS знак доллара присутствует в именах, которые применяются для наименования специальных системных функций, имена, содержащие знак доллара, резервируются для названий системных программ. Если программист случайно использует одно из этих имён, это может привести к ошибкам или непредсказуемым последствиям. Например, по макроинструкции $EXIT_S генерируется вызов системной программы SYS$EXIT. Если пользователь определит в своей программе символический адрес SYS$EXIT, то макроинструкция $EXIT_S не сможет работать нормально.

ПОЛЕ МЕТКИ

Поле метки содержит метки или имена символических адресов. Каждая метка - это символическое имя (адрес) некоторой области памяти. (Обычно остальные поля в каждой строке описывают содержимое одного или нескольких байтов, начинающихся по этому адресу.) Метка должна быть допустимым символическим именем, завершающимся двоеточием (:)[4]. По соглашению метки обычно размещаются в начале строки, но в принципе могут находиться в любом месте строки, если только перед меткой нет никаких символов, кроме пробелов. В общем случае метка в некоторой строке необходима только тогда, когда на эту строку есть ссылки из других строк программы.

ПОЛЕ КОДА ОПЕРАЦИИ

Поле кода операции может начинаться с любой позиции строки после метки, если, данный оператор имеет метку. В противном случае началом этого поля считается первый отличный от пробела символ. Однако по соглашению поле кода операции начинается с первой позиции табуляции, т.е. обычно с девятой позиции строки. В итоге коды операций начинаются с одной колонки, облегчая чтение программы. (Обычно позиции табуляции размещаются через 8 символов, т.е. в позициях 9, 17, 25, 33, ...)

Если оператор начинается с метки, в имени которой содержится более 7 символов, эту метку можно поместить на отдельной строчке, чтобы все коды операций размещались в одной колонке. Например, для записи инструкции с меткой A_LONG_NAME можно использовать две строки:

         Код
Метка   операции    Операнды

A_LONG_NAME:
        ADDL2       READ_VALUE,X_TOTAL

Некоторые программисты применяют такой формат для всех инструкций с метками независимо от длины имени метки.

Поле кода операции может содержать три типа символических имён:

  1. Мнемонические коды операций, такие как MOVL и SUBL2.
  2. Директивы ассемблера, такие как .TITLE, .BLKB и .END.
  3. Имена макроинструкций, такие как $EXIT_S.

Следует отметить, что все три типа имён удовлетворяют правилам, приведённым выше для символических имён, определяемых пользователем.

ПОЛЕ ОПЕРАНДОВ

Поле операндов может начинаться с любой позиции после поля кода операции и должно отделяться от последнего по крайней мере одним пробелом или символом табуляции. Однако по соглашению поле операндов начинается с 17-й позиции, т.е. 2-й позиции табуляции. Поле операндов состоит из некоторого числа операндов, разделённых запятыми. Число операндов зависит от типа инструкции, т.е. от содержимого поля кода операции. В директиве .BLKB требуется один операнд, в инструкции ADDB3 требуется три операнда, а в инструкции RET операнды вообще отсутствуют. Если пользователь укажет неверное число операндов, ассемблер выдаст сообщение об ошибке.

Ассемблер будет выдавать также сообщения об ошибках и при нелепых комбинациях кода операции и операндов. Например, в случае оператора

          Код
Метка    операции    Операнды

J:       .BLKL       #1

будет порождено сообщение об ошибке, поскольку знак номера (#) представляет собой спецификатор операнда, который допустим только тогда, когда в поле кода операции содержится код операции.

Аналогично оператор

           Код
Метка     операции    Операнды

FIRST:    MOVL        #5,#3  

приведёт к ошибке, поскольку бессмысленно заменять непосредственный операнд - число 3 непосредственным операндом - числом 5. (Значение константы не может меняться.)

КОММЕНТАРИИ

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

4.6. ДОПОЛНИТЕЛЬНЫЕ ВОЗМОЖНОСТИ АССЕМБЛЕРА

ПРЕОБРАЗОВАНИЕ ЧИСЕЛ

Ассемблер предоставляет программисту целый ряд дополнительных возможностей. Одной из них является преобразование чисел с различными основаниями. Обычно числа в программе на ассемблере интерпретируются как десятичные. Однако если перед числом стоят символы ^X, то это число будет рассматриваться как шестнадцатеричное. Например, действие следующих двух инструкций идентично:

MOVL    #64,K
MOVL    #^X40,K

Каждая из этих инструкций пересылает десятичное число 64 (шестнадцатеричное - 40) в длинное слово K. Именно по этой причине обозначение ^X использовалось, чтобы отличить в тексте шестнадцатеричные числа.

Аналогично если число начинается с символов ^B (binary), то оно интерпретируется как двоичное. В частности, все следующие инструкции эквивалентны:

MOVL    #24,K
MOVL    #^X18,K
MOVL    #^B11000,K

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

Неправильная запись чисел может привести к ошибкам. Ниже приведены некорректно заданные числа и указаны допущенные в них ошибки.

Число Причина ошибки

^B1012

ДВОИЧНЫЕ ЧИСЛА МОГУТ ВКЛЮЧАТЬ ТОЛЬКО ЦИФРЫ 0 И 1

1O

ИСПОЛЬЗОВАНА БУКВА 0 ВМЕСТО ЦИФРЫ 0

X1234ABCD

ПЕРЕД ЧИСЛОМ ПРОПУЩЕН СИМВОЛ (^)

123,456

ЧИСЛО СОДЕРЖИТ ЗАПЯТУЮ (,) - ЭТО НЕДОПУСТИМО

ДИРЕКТИВЫ .BYTE, .WORD И .LONG

Как уже говорилось в разд. 4.2, директива .BYTE используется для резервирования памяти и задания начального значения для данных в формате байта. Например, директивы

BOND:  .BYTE   007
MAX_BYTE:
       .BYTE   ^XFF 
UPDOWN:
       .BYTE   ^B10101010

установят для байтов с адресами BOND, MAX_BYTE и UPDOWN начальные значения 7, ^XFF (десятичное число 255) и ^B10101010 (десятичное число 170) соответственно. В процессе ассемблирования каждой строки содержимое счётчика адресов будет увеличиваться на 1, а к программе в машинном коде будет добавляться один байт информации. Наибольшее допустимое значение операнда в директиве .BYTE равно 255.

Директива .WORD очень похожа на директиву .BYTE, за исключением того, что она резервирует и задаёт начальное значение для 16-битового слова. Например, операторы

YEAR:  .WORD   1984
MAX_WORD:
       .WORD   ^XFFFF
GROSS: .WORD   144

установят начальные значения слов с адресами YEAR, MAX_WORD и GROSS равными 1984, ^XFFFF (65535 - десятичное) и 144 соответственно. При первом проходе ассемблера каждый из этих операторов вызовет увеличение содержимого счётчика адресов на 2. Во время второго прохода операнды будут преобразованы, если это необходимо, в шестнадцатеричную форму и помещены в последовательно расположенных парах байтов программы. Заметим, что ^XFFFF, или 65535, - наибольшее допустимое значение операнда в директиве .WORD.

Аналогично для инициализации 32-битовых длинных слов используется директива .LONG. В отличие от других ЭВМ в ЭВМ семейства VAX директивы .BYTE, .WORD и .LONG могут располагаться в любом порядке. Например, операторы

HOMERS:
       .BYTE   61
MAX_LONG:
       .LONG   ^XFFFFFFFF
PERMUTES:
       .WORD   5040

установят для байта с адресом HOMERS начальное значение 61, для длинного слова MAX_LONG - значение ^XFFFFFFFF, которое приблизительно равно 4 млрд, и для слова PERMUTES - значение 5040. При обработке этих операторов значение счётчика адресов увеличится на 7, при этом будет сгенерировано 7 байтов машинного кода.

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

COUNT:  .LONG  10  

Этот же результат можно получить, используя два оператора:

COUNT:  .BLKL  1
 
        . . .

        MOVL   #10,COUNT

При первом способе инициализация COUNT осуществляется только один раз во время ассемблирования. При применении второго способа инструкцию MOVL можно поместить в цикл, тогда при каждом проходе цикла значение COUNT будет установлено равным 10. Второй способ аналогичен использованию операторов COUNT=10 или COUNT:=10; языков Фортран или Паскаль. Присваивание значений первым способом аналогично действию оператора Фортрана DATA COUNT/10/. В стандартном языке Паскаль нет аналога первому способу.

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

  1. Если COUNT содержит константу, которая не меняется программой в процессе выполнения, то лучше использовать директиву .LONG, а не инструкцию MOVL; так экономится и время выполнения, и память.
  2. Если COUNT содержит переменное значение, изменяемое программой в процессе выполнения, то лучше использовать инструкцию MOVL, поскольку этот фрагмент текста программы может применяться как часть большой программы и вызываться несколько раз. Директива .LONG инициализирует соответствующим образом COUNT только перед выполнением.

ДИРЕКТИВЫ .BLKB, .BLKW И .BLKL

Как уже упоминалось, директивы .BLKB и .BLKL используются при резервировании памяти для данных в формате байтов и длинных слов. Аналогично директива .BLKW используется для резервирования слов. Рассмотрим следующие операторы:

ALPHA:  .BLKB    1
BETA:   .BLKL    1
GAMMA:  .BLKW    1
THETA:  .BLKB    1

Эти четыре директивы будут резервировать память для двух байтов (ALPHA и THETA), одного слова (GAMMA) и одного длинного слова (BETA). Все вместе они вызовут увеличение значения счётчика адресов 1 + 1 + 2 + 4 = 8.

Как описано в гл. 7, различные директивы .BLКх могут использоваться для резервирования памяти под массивы. Например, оператор

VECTOR:   .BLKL    20

означает, что VECTOR - это начальный адрес массива, состоящего из 20 длинных слов. Этот оператор увеличит значение счётчика адресов на 4 * 20 = 80.

ПРОСТЫЕ ВЫРАЖЕНИЯ

Ранее были описаны символические имена разного типа метки (символические адреса), мнемонические коды операций, директивы ассемблера и макроинструкции. Было бы желательно также применять символические имена для обозначения чисел в программе.

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

         Код
Метка   операции    Операнды

FACTORIAL_7=5040

то при каждом появлении в программе символического имени FACTORIAL_7 оно будет заменяться числом 5040. Например, для инструкции

         Код
Метка   операции    Операнды

FACTORIAL_7=5040
        . . .
        MOVL     #FACTORIAL_7,ANS

будет порождён тот же машинный код, что и для инструкции

MOVL    #5040,ANS

В процессе ассемблирования знак равенства (=) рассматривается аналогично двоеточию (:). При первом проходе ассемблера двоеточие указывает на то, что предшествующее ему имя надо занести в таблицу имён, определяемых пользователем вместе с текущим значением счётчика адресов. При обнаружении знака равенства также происходит занесение предшествующего ему имени в таблицу имён, определяемых пользователем. Однако значение, заносимое в эту таблицу, представляет собой число, стоящее справа от знака равенства. Символические имена, обозначающие числа (FACTORIAL_7), следует определять (FACTORIAL_7 = 5040) до того, как они будут использованы (MOVL #FACTORIAL_7,ANS). По соглашению все имена определяются обычно в начале программы сразу после директивы .TITLE. Определения имён (FACTORIAL_7 = 5040) могут помещаться в любом месте строки. Однако по соглашению они начинаются с начала строчки. Использование имён для обозначения чисел имеет два важных преимущества: программа становится наглядной и облегчается её сопровождение. Для того чтобы убедиться, что использование поименованных констант облегчает чтение текста программы, рассмотрим следующий оператор:

MOVL    #12,COUNT

Он показывает, что осуществляется подсчёт чего-то (COUNT - счётчик), но ничего не сообщает о том, что именно считается. При этом #12 может означать число единиц в некоторой дюжине, число дюймов в футе, число месяцев в году, число участников Тайной Вечери, число лет, начиная с которых человека можно считать подростком, и число нот в октаве. Если же пользователь определит символические имена, например, следующим образом:

DOZEN=12
INCHES_IN_FOOT=12
MONTHS_IN_YEAR=12
DISCIPLES_AT_SUPPER=12
PRE_TEEN_YEARS=12
NOTES_IN_OCTAVE=12

то он сможет применять в своей программе уже гораздо более осмысленные операторы, такие как

MOVL    #NOTES_IN_OCTAVE,COUNT

Наконец, использование символических имён для обозначения чисел позволяет облегчить сопровождение программы. Например, программа для бухгалтерского учёта и управления ресурсами в кондитерском магазине, вероятно, неоднократно использует число 12 для преобразования в дюжины единиц. Если программа должна эксплуатироваться в другом магазине, где дюжина соответствует "чертовой дюжине", то необходимо просмотреть всю программу, чтобы заменить определённые константы 12 на 13. В программе, написанной надлежащим образом, следует изменить только один оператор DOZEN=12 на DOZEN=13. Если этот пример покажется слишком надуманным, то вспомните, как много программ требуется модифицировать для перевода из английской системы мер (фунты, футы, кварты) в метрическую систему (грамм, метр, литр). Использование имён для представления чисел намного упростит такое преобразование.

Оператор DOZEN = 12 в языке ассемблера аналогичен следующим операторам языка Фортран:

Фортран Паскаль

PARAMETER (DOZEN=12)

VAR
  CONST DOZEN=12;

Определение имён в языке ассемблера с помощью оператора, подобного A = 7, кажется очень похожим на операторы присваивания в языках высокого уровня (A = 7 в Фортране или A := 7; в Паскале). Однако сходство обманчиво. В языке ассемблера все выражения вычисляются во время ассемблирования. При обработке оператора A = 7 имя и значение 7 заносится в таблицу имён. Когда ассемблер находит в дальнейшем тексте имя A, он сразу заменяет его на значение 7.

В большинстве языков высокого уровня выражения вычисляются во время выполнения программы. В процессе компиляции оператор присваивания, такой как A = 7 или A := 7, в действительности преобразуется в инструкцию, такую как MOVL #7, A. Во время выполнения эта инструкция осуществит пересылку числа 7 в ячейку памяти A.

ЧИСЛА И АДРЕСА

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

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

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

MILE=5280
        . . .
        MOVL    MILE,COUNT
  

программист случайно пропустил символ # в операнде MILE. В результате имя MILE, определённое как число (MILE=5280), используется в инструкции MOVL в качестве адреса. В процессе выполнения инструкции MOVL будет произведена попытка выборки длинного слова, расположенного по адресу 5280 (или ^X14A0). Последствия этого предсказать очень трудно. В зависимости от конкретной ситуации такая инструкция может вызвать сообщение об ошибке во время ассемблирования, сообщение об ошибке во время выполнения или просто будет получен неправильный результат.

Ошибка возникает и тогда, когда там, где не надо, указывается знак #. Например, в операторах

ALT:    .BLKL  1
        . . .  
        MOVL   #ALT,COUNT

знак # ошибочно поставлен перед операндом ALT. При выполнении инструкции MOVL операнд COUNT получает в качестве значения адрес ALT, а не его содержимое. На самом деле встречаются ситуации, в которых адреса обрабатываются как данные и ЭВМ VAX имеют специальные инструкции выполнения операций над адресами (см. гл. 7). Поэтому обычно считается, что использование знака # вместе с символическим адресом свидетельствует о плохом стиле программирования.

УПРАЖНЕНИЯ 4.1

  1. Выполните ассемблирование программ, приведённых на рис. 4.1 и 4.3, и сравните полученные листинги с листингами на рис. 4.2 и 4.4 соответственно. Выполните ассемблирование программы рис. 4.5 и сравните листинг с листингом, полученным при ассемблировании программы рис. 4.1.
  2. Выполните три программы из п. 1 убедитесь, что в байте SUM содержится число ^X63 после завершения каждой из программ.
  3. В следующей программе на языке ассемблера определите в шестнадцатеричном виде значения, которые будут содержаться в ячейках с символическими адресами от А до Н до выполнения программы.
    A:    .BYTE    20
    B:    .BYTE    128
    C:    .BYTE    -128
    D:    .WORD    128
    E:    .WORD    -128
    F:    .LONG    ^X128
    G:    .BYTE    ^B10000000
    H:    .LONG    4096
          .ENTRY   INST,0
          $EXIT_S
          .END INST
  4. Используя возможности отладчика DEBUG, с помощью команд MACRO, LINK и RUN загрузите программы п. 3 упражнения в память. Вместо команды отладчика GO (которая запускает программу на выполнение) используйте команды E/BYTE А и E/LONG F для проверки своих ответов на п. 3 упражнения (см. разд. 1.5 и приложение А).
  5. Ассемблируйте, скомпонуйте и выполните следующую программу, используя возможности отладчика DEBUG. Для того чтобы посмотреть программу в машинном коде, используйте команду отладчика E/BYTE FIRST: LAST, которая распечатывает байты памяти, расположенные между адресами FIRST и LAST. (Не забудьте, что программа загружена в память, начиная с адреса ^X0200.) Выделите коды операций, спецификаторы операндов, адреса и числа в полученной распечатке. Убедитесь в том, что относительная адресация со смещением, заданным в формате байта, обеспечивает указание соответствующих адресов данных, хотя программа начинается с адреса ^X0200.
    
    J:     .BLKL   1
    K:     .BLKL   1
    DIF:   .BLKL   1
           .ENTRY  ADDRESS,0
    FIRST: MOVL    #512,J
           MOVL    #64,K
           SUBL3   K,J,DIF
    LAST:  $EXIT_S
           .END    ADDRESS
      
  6. * В программе рис. 4.3 ассемблер для адресации операндов A, B, C и SUM использовал относительную адресацию со смещением, заданным в формате длинного слова, поскольку эти символические адреса были определены в конце программы. Возможно ли разработать трёхпроходный ассемблер, позволяющий использовать там, где допустимо, смещение в формате байта или слова - даже в случае, когда адреса встречаются до того, как они определены?

4.7. КОМПОНОВЩИК

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

В принципе программные модули можно написать на разных языках программирования. Например, основная программа А может представлять собой программу в машинном коде, полученную в результате трансляции с языка Фортран, а подпрограмма В может быть результатом трансляции с языка ассемблера. Разбивая сложную задачу на небольшие подзадачи, программист может выбрать для решения каждой подзадачи наиболее подходящий язык.

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

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

Обычно предполагается, что программа на ассемблере должна быть обработана компоновщиком, даже если она состоит только из одного модуля. Программа сразу после ассемблирования не может быть выполнена. Программа, полученная в результате работы ассемблера, называется объектным модулем (object module) и хранится в объектном файле, имеющем тип OBJ. Результат работы компиляторов языков высокого уровня, таких как Фортран или Паскаль, имеет такой же формат, и помещается в файлы типа OBJ.

Компоновщик считывает файлы типа OBJ, перемещает и связывает объектные модули, чтобы сформировать единую, готовую к выполнению программу. Программа в этом виде называется выполняемым образом (executable image) и хранится в файле выполняемого образа, имеющем тип EXE.

В конце гл. 3 было отмечено, что относительная адресация по сравнению с абсолютной даёт кроме экономии памяти и времени, другие существенные преимущества: использование относительной адресации внутри модуля означает то, что относительные адреса не должны модифицироваться при перемещении этого модуля. Внутри модуля расстояние (смещение) между инструкциями и данными, к которым они обращаются, не должно изменяться при перемещении модуля. Напротив, если в модуле используется абсолютная адресация, то при перемещении этого модуля все абсолютные адреса будут изменены. Поэтому абсолютные адреса в объектном модуле должны быть помечены ассемблером, для того чтобы компоновщик смог модифицировать их при создании выполняемого образа. (Модули, в которых не требуется модификация адресов при компоновке, называются позиционно независимыми. Эта важная тема подробно рассматривается в гл. 7 и 15.)

4.8. ВЫПОЛНЕНИЕ ПРОГРАММЫ

ПРИМЕР ПРОГРАММЫ

В качестве иллюстрации рассмотрим процесс ассемблирования, компоновки и выполнения программы, написанной на языке ассемблера. Предполагается, что читатель должен использовать терминал некоторого типа. В рассматриваемом примере программист зарегистрирован в системе под именем JOHNDOE. Предполагается, что он с помощью одного из редакторов текста, имеющихся на вычислительных системах VAX, создал исходный файл с именем SAMPLE.MAR. Если теперь программист введёт с терминала команду DIRECTORY (каталог) или просто DIR, то на экране дисплея появится

$ DIR

Directory DISK$USER: [JOHNDOE]

SAMPLE.MAR;1

Total of 1 file.
$

Символы, которые вводит с клавиатуры пользователь, выделяются подчёркиванием. Знак доллара $ в последней строке служит признаком того, что операционная система VAX/VMS готова принять очередную команду.

Пользователь может посмотреть содержимое файла SAMPLE.MAR, выведя его на свой терминал с помощью команды ТУРЕ:

$ TYPE SAMPLE.MAR
        .TITLE   SQUARES  -ТАБЛИЦА КВАДРАТНЫХ КОРНЕЙ-
        .DISABLE GLOBAL                        ; УКАЗАТЬ НЕОПРЕДЕЛЁННЫЕ ИМЕНА
; ВЫЧИСЛИТЬ ТАБЛИЦУ КВАДРАТНЫХ КОРНЕЙ С ПОМОЩЬЮ МЕТОДА КОНЕЧНЫХ РАЗНОСТЕЙ
; АВТОР - ДЖОН ДОУ
; ДАТА СОЗДАНИЯ - ИЮЛЬ 31,1985
SECOND_DIF=2                                   ; КОНСТАНТА - ВТОРАЯ РАЗНОСТЬ
FIRST_DIF_INIT=1                               ; НАЧАЛЬНАЯ ПЕРВАЯ РАЗНОСТЬ
SQUARE_INIT=1                                  ; КОРЕНЬ ИЗ ЕДИНИЦЫ
FIRST_DIF:                                     ; ПЕРЕМЕННАЯ - ПЕРВАЯ РАЗНОСТЬ
        .BLKL    1
SQUARE_1:                                      ; СОДЕРЖИТ ЗНАЧЕНИЕ ПЕРВОГО КОРНЯ
        .BLKL    1
SQUARE_2:                                      ; СОДЕРЖИТ ЗНАЧЕНИЕ ВТОРОГО КОРНЯ
        .BLKL    1
SQUARE_3:                                      ; СОДЕРЖИТ ЗНАЧЕНИЕ ТРЕТЬЕГО КОРНЯ
        .BLKL    1
        .ENTRY   BEGIN,0                       ; ТОЧКА ВХОДА
        MOVL     #FIRST_DIF_INIT,FIRST_DIF     ; ЗАДАТЬ ПЕРВУЮ РАЗНОСТЬ
        MOVL     #SQUARE_INIT,SQUARE_1         ; ВЫЧИСЛИТЬ ПЕРВЫЙ КОРЕНЬ
        ADDL2    #SECOND_DIF,FIRST_DIF         ; СКОРРЕКТИРОВАТЬ ПЕРВУЮ РАЗНОСТЬ
        ADDL3    FIRST_DIF,SQUARE_1,SQUARE_2   ; ВЫЧИСЛИТЬ ВТОРОЙ КОРЕНЬ
        ADDL2    #SECOND_DIF,FIRST_DIF         ; СКОРРЕКТИРОВАТЬ ВТОРУЮ РАЗНОСТЬ
        ADDL3    FIRST_DIF,SQUARE_2,SQUARE_3   ; ВЫЧИСЛИТЬ ТРЕТИЙ КОРЕНЬ
        $EXIT_S                                ; ЗАВЕРШИТЬ ПРОГРАММУ
        .END     BEGIN
$

Эта программа вычисляет квадраты чисел от единицы до трёх с помощью метода разностей. В основу этого метода положен тот факт, что разности между квадратами соседних чисел натурального ряда образуют ряд 1, 3, 5, 7, ... Заметим, что разность между смежными членами последнего ряда постоянна и равна 2. Иллюстрация этого метода приведена ниже.

Квадраты 0 1 4 9 16 ...
Первая разность 1 3 5 7 ...
Вторая разность 2 2 2 ...

Для вычисления каждого следующего члена ряда квадратов надо прибавить 2 к последней вычисленной разности (например, 7 + 2 = 9). Полученный результат надо прибавить к последнему вычисленному квадрату (9 + 16 = 25).

АССЕМБЛИРОВАНИЕ ПРОГРАММЫ

Ассемблирование программы, написанной на языке ассемблера, производится по команде $MACRO/DEBUG/LIST SAMPLE. Параметры /DEBUG и /LIST, следующие за командой MACRO, устанавливают дополнительные режимы работы ассемблера. Параметр /DEBUG информирует ассемблер о том, что пользователю потребуются средства отладки во время выполнения программы. Параметр /LIST указывает ассемблеру, что требуется выдать листинг программы как в машинном коде, так и на языке ассемблера.

Результат выполнения команды MACRO можно обнаружить, просмотрев каталог:

$ MACRO/DEBUG/LIST SAMPLE
$ DIR

Directory DISK$USER: [JOHNDOE]

SAMPLE.LIS;1         SAMPLE.MAR;1         SAMPLE.OBJ;1

Total of 3 files.
$

Новый файл SAMPLE.OBJ содержит вариант программы в машинном коде. Не надо пытаться вывести этот файл на терминал, он имеет не символьный, а двоичный формат. Ещё один файл, SAMPLE.LIS, содержит листинг программы на языке ассемблера. По умолчанию ассемблер присваивает объектному файлу тип .OBJ, файлу листинга - тип .LIS. Так как файл SAMPLE.LIS символьный, его содержимое можно вывести на терминал пользователя:

$ TYPE SAMPLE.LIS
     SQUARES                             -ТАБЛИЦА КВАДРАТНЫХ КОРНЕЙ-              24-JUN-1985 20:00:43  VAX-11 Macro V03-00        Page    1
                                                                                  24-JUN-1985 19:54:02  DISK$USER:[JOHNDOE]SAMPLE.MAR; 1 (1)
                                              0000     1        .TITLE   SQUARES -ТАБЛИЦА КВАДРАТНЫХ КОРНЕЙ-
                                              0000     2        .DISABLE GLOBAL                         ; УКАЗАТЬ НЕОПРЕДЕЛЁННЫЕ ИМЕНА
                                              0000     3 ;ВЫЧИСЛИТЬ ТАБЛИЦУ КВАДРАТНЫХ КОРНЕЙ С ПОМОЩЬЮ МЕТОДА КОНЕЧНЫХ РАЗНОСТЕЙ
                                              0000     4 ;АВТОР - ДЖОН ДОУ
                                              0000     5 ;ДАТА СОЗДАНИЯ - ИЮЛЬ 31, 1985
                                    00000002  0000     6 SECOND_DIF=2                                   ;КОНСТАНТА  - ВТОРАЯ РАЗНОСТЬ
                                    00000001  0000     7 FIRST_DIF_INIТ=1                               ; НАЧАЛЬНАЯ ПЕРВАЯ РАЗНОСТЬ
                                    00000001  0000     8 SQUARE_INIT=1                                  ; КОРЕНЬ ИЗ ЕДИНИЦЫ
                                              0000     9 FIRST_DIF:                                     ; ПЕРЕМЕННАЯ - ПЕРВАЯ РАЗНОСТЬ
                                    00000004  0000    10         .BLKL   1
                                              0004    11 SQUARE_1:                                      ; СОДЕРЖИТ ЗНАЧЕНИЕ ПЕРВОГО КОРНЯ
                                    00000008  0004    12         .BLKL   1
                                              0008    13 SQUARE_2:                                      ; СОДЕРЖИТ ЗНАЧЕНИЕ ВТОРОГО КОРНЯ
                                    0000000С  0008    14         .BLKL   1
                                              000С    15 SQUARE_3:                                      ; СОДЕРЖИТ ЗНАЧЕНИЕ ТРЕТЬЕГО КОРНЯ
                                    00000010  000С    16         .BLKL   1
                                        0000  0010    17         .ENTRY  BEGIN,0                        ; ТОЧКА ВХОДА
                             ЕА AF   01   D0  0012    18         MOVL    #FIRST_DIF_INIT,FIRST_DIF      ; ЗАДАТЬ ПЕРВУЮ РАЗНОСТЬ
                             EA AF   01   D0  0016    19         MOVL    #SQUARE_INIT,SQUARE_1          ; ВЫЧИСЛИТЬ ПЕРВЫЙ КОРЕНЬ
                             E2 AF   02   С0  001A    20         ADDL2   #SECOND_DIF,FIRST_DIF          ; СКОРРЕКТИРОВАТЬ ПЕРВУЮ РАЗНОСТЬ
               E3 AF   E1 AF      DF AF   C1  001E    21         ADDL3   FIRST_DIF,SQUARE_1,SQUARE_2    ; ВЫЧИСЛИТЬ ВТОРОЙ КОРЕНЬ
                             D7 AF   02   С0  0025    22         ADDL2   #SECOND_DIF,FIRST_DIF          ; СКОРРЕКТИРОВАТЬ ВТОРУЮ РАЗНОСТЬ
               DС AF   DA AF      D4 AF   Cl  C029    23         ADDL3   FIRST_DIF,SQUARE_2,SQUARE_3    ; ВЫЧИСЛИТЬ ТРЕТИЙ КОРЕНЬ
                                              0030    24         $EXIT_S                                ; ЗАВЕРШИТЬ ПРОГРАММУ
                                              0039    25         .END    BEGIN

SQUARES                         -ТАБЛИЦА КВАДРАТНЫХ КОРНЕЙ-                24-JUN-1985  20:00:43  VAX-11 Macro V03-00        Page    2
     Symbol table                                                               24-JUN-1985 19:54:02  DISK$USER:[JOHNDOE] SAMPLE. MAR;1 (1)
     BEGIN            00000010 RG D  01
     FIRST_DIF        00000000 R  D  01
     FIRST_DIF_INIT = 00000001    D
     SECOND_DIF     = 00000002    D
     SQUARE_1         00000004 R  D  01
     SQUARE_2         00000008 R  D  01
     SQUARE_3         0000000C R  D  01
     SQUARE_INIT    = 00000001    D
     SYS$EXIT         ********  G    01
                                                     +----------------+
                                                     ! Psect synopsis !
                                                     +----------------+
                                              (данная секция листинга пропущена)
                                                  +------------------------+
                                                  ! Performance indicators !
                                                  +------------------------+
                                              (данная секция листинга пропущена)
                                                 +--------------------------+
                                                 ! Macro library statistics !
                                                 +--------------------------+
                                              (данная секция листинга пропущена)
There were no errors, warnings or information messages.
/DEBUG/LIST SAMPLE
$

Листинг ассемблерной программы и программы в машинном коде достаточно понятен, за исключением строк 18, 19, 20 и 22. В соответствии с изложенным выше можно было ожидать, что при обработке оператора ассемблера

MOVL    #FIRST_DIF_INIТ,FIRST_DIF

будет сгенерирован код

Операнд Операнд Код операции Адрес

ЕЕ AF

00000001

8F D0

0012

где ^X0012 - начальный адрес, ^X8F - спецификатор операнда (байт) непосредственной адресации, ^X00000001 - число, соответствующее имени FIRST_DIF_INIT. Однако в строке 18 сгенерирован следующий код:

Операнд Операнд Код операции Адрес

ЕЕ AF

01

D0

0012

Вообще говоря, существует два способа задания непосредственных операндов, требующих меньше и больше памяти. Первый - наиболее экономный способ применим только для небольших чисел от 0 до 63. Любые другие числа задаются вторым способом со спецификатором операнда ^X8F. Первый способ задания называется литеральным режимом адресации и позволяет хранить число непосредственно в байте спецификатора операнда. Для задания таких чисел используются спецификаторы операндов со значениями от ^X00 до ^X3F. Более подробно литеральный режим адресации описан в гл. 7.

После распечатки программы ассемблер печатает таблицу имён, определённых пользователем. Строка

FIRST_DIF    00000000  R  D  01

в этой таблице сообщает, что значениям символического имени FIRST_DIF является ^X00000000. Стоящая далее буква R показывает, что FIRST_DIF - переместимый адрес, т.е. адрес будет настраиваться при перемещении программы. Буква D означает, что это символическое имя доступно отладчику. Число 01 - ссылка на программную секцию (см. гл. 9).

Переместимый адрес BEGIN отличается от остальных адресов в программе, поскольку он определён директивой .ENTRY. Это означает, что хотя адрес BEGIN определяется в данной программе, но на него можно ссылаться в других программах (например, из операционной системы VAX/VMS). Символические имена, которые определены в данной программе, но доступны другим программам, называются глобальными именами (global symbol). В данном случае буквы RG означают, что BEGIN - это переместимое глобальное имя. В последней строке таблицы имён содержится запись

SYS$EXIT     ******** G      01

Это имя генерируется макроинструкцией $EXIT_S, которая используется для возврата управления операционной системе VAX/VMS. Макроинструкция $EXIT_S в действительности генерирует инструкции, передающие управление по адресу SYS$EXIT, значение которого определяется операционной системой VAX/VMS. Поскольку адрес SYS$EXIT используется в этой программе, а определён в другой, он обозначается как глобальный (G). Символы ******** означают, что имя SYS$EXIT в настоящий момент не определено. Более подробно глобальные имена описаны в гл. 9.

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

.DISABLE   GLOBAL   ; УКАЗАТЬ НЕОПРЕДЕЛЁННЫЕ ИМЕНА

При наличии такого оператора ассемблер генерирует сообщение об ошибке каждый раз, когда обнаруживает неопределённое имя в программе пользователя. (Имя SYS$EXIT не вызовет сообщения об ошибке, поскольку в макроинструкции $EXIT_S оно определено как внешнее.)

Теперь можно описать действия, выполняемые при указании параметра /DEBUG, команды MACRO. Когда этот параметр отсутствует, то в создаваемый объектный файл ассемблер включает информацию только о глобальных именах. Оставшаяся часть таблицы имён теряется с окончанием ассемблирования. Параметр /DEBUG указывает ассемблеру на необходимость сохранить в объектном файле таблицу имён целиком. Это позволяет программисту использовать при отладке символические имена.

КОМПОНОВКА ПРОГРАММЫ

Программа компонуется по команде операционной системы VAX/VMS LINK/DEBUG SAMPLE. Результат выполнения команды LINK обнаруживается в каталоге пользователя:

$ LINK/DEBUG SAMPLE
$ DIR

Directory DISK$USER:[JOHNDOE]

SAMPLE.EXE;1       SAMPLE.LIS;1       SAMPLE.MAR;1       SAMPLE.OBJ;1

Total of 4 files.
$
  

Новый файл SAMPLE.EXE - это выполняемый вариант пользовательской программы в машинном коде, В процессе преобразования объектной программы SAMPLE.OBJ в выполняемую программу SAMPLE.EXE компоновщик настраивает программу так, чтобы она начиналась с адреса ^X0200, а не с адреса ^X0000. Процесс настройки очень прост, поскольку в программе используется только относительная адресация. Причины, по которым производится эта настройка, описаны в разделе, посвящённом ошибкам программирования и отладке. Параметр /DEBUG в команде LINK указывает на то, что программисту потребуются средства отладки при выполнении программы SAMPLE.EXE.

ВЫПОЛНЕНИЕ ПРОГРАММЫ

Программу пользователя можно запустить на выполнение, введя команду RUN SAMPLE. Так как при компоновке к программе были присоединены средства отладки (был указан параметр /DEBUG в команде LINK), то теперь управление передаётся не пользовательской программе, а программе-отладчику DEBUG. (Отладчик можно также активизировать командой RUN/DEBUG SAMPLE.) Отладчик выводит на терминал две строки информации, а затем сообщение DBG):

$ RUN SAMPLE
    
         VAX-11 DEBUG Version V3.4-2
         
%DEBUG-I-INITIAL,language is MACRO,module set to 'SQUARES'
DBG>

Точно так же, как операционная система VAX/VMS использует символ $ как приглашение к вводу следующей команды операционной системы, так и отладчик использует сообщение DBG>: для указания того, что пользователю следует вводить команду отладчика. Обычно в этот момент вводят команду GO, по которой начинается выполнение программы пользователя.

DBG>GO
routine start at SQUARES\BEGIN
%DEBUG-I-EXITSTATUS,is '%SYSTEM-S-NORMAL, normal successful completion'
DBG>
    

На этом этапе обычно просматривают содержимое памяти по команде Е (Examine - посмотреть) , например

DBG>E SQUARE_1
SQUARES\SQUARE_1:    00000001
DBG>E SQUARE_2
SQUARES\SQUARE_2:    00000004
DBG>E/DEC SQUARE_3
SQUARES\SQUARE_3:    9
DBG>
  

По команде E SQUARE_1 выводится на терминал содержимое длинного слова SQUARE_1 в программе SQUARES (SQUARES\SQUARE_1), оно равно ^X00000001; по второй команде Е SQUARE_2 выводится содержимое длинного слова SQUARE_2, равное ^X00000004. Наконец, по команде E/DEC SQUARE_3 выводится содержимое адреса SQUARE_3 в десятичном виде; число 9 соответствует ожидаемому результату. Отметьте, что в то время, как по умолчанию в ассемблере используется десятичная система счисления, в отладчике по умолчанию числа выводятся в шестнадцатеричном виде.

С помощью отладчика можно посмотреть область памяти, где расположены инструкции. Например, по команде

DBG>E BEGIN+2
SQUARES\BEGIN+2:    0EAAF01D0
DBG>

выдаётся машинный код первой инструкции программы. (Так как BEGIN - точка входа программы, этот адрес является адресом 16-битовой маски (два байта). Первая инструкция расположена непосредственно за маской, поэтому её адрес BEGIN+2. Инструкция располагается в длинном слове справа налево: ^XD0 - код операции, ^X01 - литеральный операнд ^X01, ^XAF - спецификатор второго операнда и ^X0E - смещение, заданное в формате байта.

Отладчик не может определить, что содержится по данному адресу: байт, слово, длинное слово или инструкция. Поэтому, если не указано что-либо другое, по команде EXAMINE отображается содержимое длинного слова Содержимое байтов и слов можно посмотреть с помощью команд E/BYTE и Е/WORD соответственно. Командой Е/INST можно посмотреть инструкцию в символьном формате. Поскольку в команде MACRO присутствовал параметр /DEBUG, отладчику доступна таблица имён, а это означает, что он может частично восстановить по машинному коду заданный оператор исходной ассемблерной программы, например

DBG>E/INST BEGIN+2
SQUARES\BEGIN+2:        MOVL #01,B^SQUARES\FIRST_DIF
DBG>
    

Обратите внимание, что второй операнд инструкции начинается с символов В^. Эти символы означают, что для имени FIRST_DIF в программе SQUARES используется относительная адресация с заданным в формате байта смещением.

Команда Examine может использоваться для просмотра содержимого памяти по абсолютным адресам. Следующие шесть команд позволяют посмотреть содержимое различных ячеек памяти с адресами от ^X0000 до ^X020A:

DBG>E 0
%DEBUG-E-N0ACCESSR,no read access to virtual address 00000000
DBG>E 1FF
%DEBUG-E-N0ACCESSR,no read access to virtual address 000001FF
DBG>E 200
SQUARES\FIRST_DIF:      00000005
DBG>E 204
SQUARES\SQUARE_1:       00000001
DBG>E/WORD 210
SQUARES\BEGIN:  0000
DBG>E/INST 212
SQUARES\BEGIN+2:        MOVL    #01,B^SQUARES\FIRST_DIF
DBG>EXIT
$

Как показывают первые две команды, невозможно посмотреть содержимое памяти с адреса ^X0000 до адреса ^X01FF. Программисту эти адреса не нужны, поскольку компоновщик размещает программу с адреса ^X0200. Третья команда Е 200 выводит на экран содержимое длинного слова с символическим адресом FIRST_DIF. В результате перемещения программы абсолютный адрес FIRST_DIF равен ^X0200. При указании соответствующих адресов выводится содержимое ячеек с именами SQUARE_1. BEGIN и BEGIN+2. Наконец, команда отладчика EXIT возвращает управление операционной системе VAX/VMS.

ОШИБКИ ПРОГРАММИРОВАНИЯ И ОТЛАДКА

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

MOVL    #SQUARE_INIT,SQUARE_1    ; ВЫЧИСЛИТЬ ПЕРВЫЙ КОРЕНЬ

В приведённой ниже строке вместо кода операции MOVL ошибочно указан код MOV:

MOV    #SQUARE_INIT,SQUARE_1    ; ВЫЧИСЛИТЬ ПЕРВЫЙ КОРЕНЬ

В результате ассемблер не может найти значение для имени MOV и выдаёт следующее сообщение об ошибке (неопределённый оператор) :

$ MACRO SAMPLE
                                     0016    19         MOV     #SQUARE_INIT,SQUARE_1
korenx
%MACRO-E-UNRECSTMT, Unrecognised statement

There were 1 error, 0 warnings and 0 information messages, on lines:
   19 (1)
    

Сходное сообщение будет выдано, если после метки будет пропущено двоеточие.

Весьма различные ошибки могут возникать при использовании числа в качестве адреса. Например, в следующей строке программист забыл поставить знак # перед именем SQUARE_INIT:

MOVL     SQUARE_INIT,SQUARE_1    ; ВЫЧИСЛИТЬ ПЕРВЫЙ КОРЕНЬ

Ни ассемблер, ни компоновщик не сообщает об ошибке. Во время выполнения программа попытается произвести выборку длинного слова, расположенного по абсолютному адресу ^X00000001. Так как пользователю адреса, меньшие ^X0200, недоступны, то выдаётся следующее сообщение об ошибке (нарушение доступа):

$ MACRO SAMPLE
$ LINK SAMPLE
$ RUN SAMPLE
%SYSTEM-F-ACCVIO, access violation, reason mask=00, virtual address=00000001, PC=0000021
%TRACE-F-TRACEBACK, symbolic stack dump follows
module name     routine name                     line       rel PC    abs PC
SQUARES         . BLANK .                                   0000001A  0000021A
  

По этому сообщению программист должен найти оператор, который, по всей вероятности, сгенерировал запрещённый виртуальный адрес ^X00000001. После символов abs PC распечатывается адрес ^X0000021A, который содержался в программном счётчике в момент обнаружения ошибки. Просмотр содержимого памяти вблизи адреса ^X021A с помощью отладчика может помочь локализовать ошибку. Адрес ^X0000001A, выведенный после символов rel PC, - это тот же адрес, но относительно начала модуля. Просмотр адресов вблизи адреса ^X001A по распечатке ассемблерной программы также может помочь обнаружить ошибку. Адрес abs PC больше адреса rel PC на ^X200, так как компоновщик настроил модуль на адрес ^X0200.

К генерации младших запрещённых адресов может привести множество ошибок программирования, в том числе и пропуск символа #. Если программа пытается обратить по адресу, меньшему ^X0200, то операционная система VAX/VMS прекращает выполнение программы. Фактически поэтому в операционной системе VAX/VMS перемещается начало основной программы в адрес ^X0200, а младшие адреса делаются недоступными для пользователя.

Использование адресов в качестве чисел может привести к тому, что программы будут выполняться без ошибок, но при этом будут выдавать неправильные результаты. Например, рассмотрим, что произойдёт, если в строке 6 заменить SQUARE_INIT=1 на SQUARE_INIT=^X210. Если ассемблировать, скомпоновать и запустить на выполнение такую программу, то она благополучно завершится без выдачи сообщений об ошибке. Однако значения переменных SQUARE_1, SQUARE_2 и SQUARE_3 будут совершенно неверными. Пусть программист, используя число как адрес, случайно сгенерировал допустимый адрес. Программа пытается произвести выборку длинного слова, расположенного по абсолютному адресу ^X0210. Так как программа начинается с адреса ^X0200, то искомое длинное слово размещается на ^X10 байт дальше от начала программы и содержит ^X01D00000. Первые четыре шестнадцатеричные цифры ^X01D0 - это первые байты машинного кода инструкции (см. строку 18 распечатки), а последние четыре цифры ^X0000 - это 16-битовая маска, сгенерированная в строке 17.

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

УПРАЖНЕНИЯ 4.2

  1. Определите с помощью команд отладчика Е1000, Е2000, Е4000, Е8000, Е10000 и т. д. самый старший адрес, к которому вам разрешён доступ на вашей вычислительной системе VAX. Можете ли вы использовать отладчик DEBUG для преобразования полученных шестнадцатеричных значений в десятичные числа?
  2. Ассемблируйте, скомпонуйте и выполните программу, которая вычисляет квадраты чисел 1, 2, 3, и с помощью отладчика убедитесь в её правильной работе. Затем повторите этот процесс, внося в программу одну из нижеперечисленных ошибок. Опишите, к чему приводит каждая из ошибок. Порождает ли эта ошибка сообщения при ассемблировании, компоновке, выполнении или просто приводит к неправильным результатам?
    • а)  удалите двоеточие после метки FIRST_DIF в строке 9;
    • б)  замените метку в строке 9 с FIRST_DIF на FIRST_DIFF;
    • в)  удалите директиву .DISABLE GLOBAL в строке 2 и повторите пункт б);
    • г)  удалите оператор .BLKL 1 в строке 14 (одной строкой ниже метки SQUARE_2);
    • д)  удалите оператор $EXIT_S в строке 24.
  3. Измените программу, вычисляющую квадраты чисел 1, 2, 3 так, чтобы она вычисляла квадраты чисел от 1 до 6.
  4. Напишите программу по образцу программы SQUARES, которая вычисляет кубы целых чисел от 1 до 6. (Подсказка: для описания алгоритма выпишите ряд кубов 1, 8, 27, 64, 125, ..., а затем на бумаге вычислите разности первого, второго и третьего порядков.)

 

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


[1] Адрес, с которого начинается выполнение программы. - Прим. ред.

[2] Символические имена, выбранные как из поля кода операции, так и из поля операндов, ассемблер VAX-11 MACRO пытается прежде найти по таблице имён, определяемых пользователем. Если в ней отсутствует данное символическое имя, то ассемблер просматривает таблицу постоянных имён. Такой порядок обращения к таблицам позволяет пользователям переопределять символические имена, описанные в таблице постоянных имён.

[3] Ассемблер разрешает программисту явно переопределять некоторые правила, принимаемые в языке ассемблера по умолчанию. В данном случае программист может задать смещение в формате байта, слова или длинного слова для относительной адресации с помощью префиксов В^ W^ и L^, помещаемых перед операндом. В частности, в инструкции ADDB3 В^X, W^Y, L^Z ассемблеру даётся указание, что для относительной адресации X, Y и Z надо использовать смещение в формате байта, слова и длинного слова соответственно.

[4] Допустимы пробелы и символы табуляции между именем и двоеточием. Это позволяет программисту при желании выравнивать двоеточия в разных строках.