
Assembler / P16
.pdf1
16.1. Директивы повторения
Таких директив три: REPT, IRP, IRPC. Сначала изучим директиву REPT.
REPT количество_повторений блок
ENDM
Пример. Сгенерировать 12 первых нечетных положительных чисел (разместить в байтовом массиве). В программе поместить в AL пятый элемент массива.
.MODEL small
.STACK 100h
.DATA
odd_num LABEL byte I = -1
REPT 12
I = I + 2
DB I ENDM
.CODE
s:mov ax,@data mov ds,ax
mov al, odd_num + 5 * TYPE odd_num exit
END s
Таким образом таблица с нечетными числами будет создана на этапе ассемблирования.
Для того чтобы в программе можно было обращаться к области памяти, содержащей таблицу, в секции данных введена метка odd_num с помощью директивы LABEL. В директиве указан тип помеченных данных — байт. В секции кода в регистр AL загружается пятый элемент таблицы (нумерация, как и в языке Си, идет с нуля). Функция времени ассемблирования TYPE возвращает количество байтов, отводимое под один элемент данных. Для байтов возвращается 1, для слов — 2, для двойных слов 4 и т.д. Поэтому, если мы заменим в секции данных тип BYTE на тип WORD (и директивы DB — на DW), то в секции кода нужно только заменить AL на AX.
Упражнение. Изучите листинг программы. Выполните ее по шагам в TD. Пример. Сгенерировать на этапе ассемблирования строку "abcde…xyz".
.MODEL small
.STACK 100h
.DATA
abc LABEL byte
L = 'a'
REPT 26
DB L
L = L + 1
ENDM
.CODE
s:
END s
2
В отладчике перейдите в панель кода и после Ctrl+G введите имя abc. Вы увидите расположенные в памяти буквы алфавита.
Задача. Сгенерировать на этапе ассемблирования строку "abcde…xyzAB…YZ". Вывести ее на экран посредством функции 09h прерывания 21h.
Задача. Сгенерировать на этапе ассемблирования 12 первых чисел ряда Фибоначчи. Разместить их в словах.
16.2. Условное ассемблирование
Вязыке Ассемблера имеется возможность генерировать те или иные команды в зависимости от выполнения некоторых условий. Мы уже видели условный оператор IFNB, использованный при написании макрокоманды exit.
Вязыке Ассемблера есть несколько разновидностей условных операторов. Наиболее общим является следующий
IF условие
условный блок
ELSE
альтернативный условный блок
ENDIF
Ветвь ELSE и альтернативный условный блок могут отсутствовать. Если нужно большее количество условных блоков, то ELSE заменяется на
ELSEIF условие
В условии можно использовать выражения и операторы сравнения: EQ (равно), NE (не равно), LT (меньше), LE (меньше или равно), GT (больше), GE (больше или равно). Из результатов сравнения можно образовывать логические выражения с помощью операторов: NOT, AND, OR, XOR.
Пример. Генерировать таблицу: из первых 40 нечетных чисел поместить в таблицу только те, которые делятся на 3, но не делятся на 5. Приведем фрагмент программы
…
I = -1
REPT 40
I = I + 2
IF I MOD 3 EQ 0 AND I MOD 5 NE 0 DB I
ENDIF ENDM
…
Если бы задача была сформулирована чуть иначе: генерировать таблицу первых 40 нечетных чисел, которые делятся на 3, но не делятся на 5. Тогда пришлось бы воспользоваться оператором
WHILE выражение блок
ENDM
Блок повторяется, пока выражение не станет равным нулю. Решение:
I = -1
3
K = 40
WHILE K
I = I + 2
IF I MOD 3 EQ 0 AND I MOD 5 NE 0
DB I
K = K - 1
ENDIF
ENDM
Упраженение. Почему для этой программы при трансляции выдаются сообщения об ошибках? Какое можно предложить исправление?
IRP — блок повторений с переменным числом параметров (Indefinite Repeat Block). Его формат:
IRP param, <value1, value2,…>
блок
ENDM
На первом шаге генерируется блок, в котором вместо param подставляется значение value1, затем блок, в котором вместо param подставляется value2, и т.д. до исчерпания списка. Элементы списка значений могут быть числами, символами, строками.
Пример. Создать массив слов, содержащих квадраты первых десяти простых чисел.
IRP V,<1,2,3,5,7,11,13,17,19,23> DW V*V
ENDM
(Можно было, конечно, и вручную такую таблицу рассчитать, но пусть лучше Ассемблер трудится.)
Пример. Поместить в стек регистры AX, BX, CX, DX.
IRP reg, <ax, bx, cx, dx> push reg
ENDM
Будут сгенерированы команды push ax
push bx push cx push dx
Такая конструкция полезна для MASM. В TASM можно написать push ax bx cx dx (имена регистров разделены пробелом)
и будет сгенерирована упомянутая последовательность команд. (Аналогичная конструкция имеется для команды pop)
Имеется разновидность IRP: директива IRPC — IRP of Characters — блок повторения символов.
Пример. Заполнить массив байтов символами 'p', 'r', 'o', 'g'. Можно для этого воспользоваться IRP:
IRP sym, <'p','r','o','g'> DB sym
ENDM
Но проще так:
4
IRPC sym, prog DB sym
ENDM
Разумеется, проще написать DB 'prog'. Пример надуманный.
Но в сочетании с еще одним средством ассемблера — конкатенацией символов
— IRPC может быть полезен. Вернемся к примеру
N1 |
= 5 |
|
|
N2 |
= N1+2 |
|
|
N3 |
= 2*N1-1 |
|
|
N4 |
= N1 MOD 3 |
||
m1 |
DW |
N1 |
DUP(0) |
m2 |
DW |
N2 |
DUP(0) |
m3 |
DW |
N3 |
DUP(0) |
m4 |
DW |
N4 |
DUP(0) |
Последние четыре строки можно сгенерировать так:
IRPC k, 1234
m&k DW N&k DUP(0) ENDM
16.3. Макрокоманды
Мы будем вводить макросы на примере нашей первой программы first.asm.
Вот ее текст. |
|
|
|
.MODEL small |
|
|
.STACK 100h |
|
|
.DATA |
|
msg |
DB "Hello!",0Dh,0Ah,'$' |
|
|
.CODE |
|
start: mov |
ax,@data |
|
|
mov |
ds,ax |
|
mov |
dx,OFFSET msg |
|
mov |
ah,09h |
|
int |
21h |
|
mov |
ah,4Ch |
|
int |
21h |
|
END |
start |
При выполнении упражнений вы будете изменять текст программы. Макросы позволяют как бы расширить язык машинных команд.
Определение макрокоманды имеет вид:
имя MACRO формальные_параметры
<тело макрокоманды>
ENDM
Обратите внимание, что перед директивой ENDM имя макроса не указывается.
Вызов макрокоманды имеет вид
имя фактические_параметры
Вызов макрокоманды Ассемблер заменяет ее телом, подставляя вместо формальных параметров фактические.
Пример. Макрос для вывода строки на экран: message MACRO string
mov dx, OFFSET string
5
mov ah, 09h int 21h ENDM
Теперь для вывода строки msg достаточно поместить в начале файла first.asm это определение, а в секции кода — message msg. При развертывании макроса вместо этого вызова Ассемблер разместит три строки, составляющие тело макрокоманды, а формальный параметр string заменит фактическим параметром msg.
Упражнение. Что произойдет при трансляции, если заменить ENDM на message ENDM? Если закомментировать строку ENDM?
Можно ввести макрооопределение:
DOS MACRO func mov ah,func int 21h ENDM
Тогда предыдущее макроопределение можно переписать так: message MACRO string
mov dx, OFFSET string DOS 09h
ENDM
Такие макросы называются вложенными.
Упражнение. Посмотрите, как в листинге отображается вложенность макросов.
Пример. Макрос для помещения в стек регистров из списка push_regs MACRO reg_list
IRP reg, <reg_list> push reg
ENDM ENDM
Обращение к такой макрокоманде имеет вид: push_regs <ax,cx,si,di>
Здесь угловые скобки обязательны. Если их опустить, то получится, что у макроса четыре аргумента, а их всего один — список регистров, воспринимаемый как единое целое.
Как уже говорилось, в программе для TASM вводить такой макрос не имеет смысла.
Сложнее реализуется макрокоманда для завершения работы программы. Напрашивается решение:
exit MACRO return_code mov al,return_code DOS 4Ch
ENDM
Но, как правило, мы заканчиваем программу с нулевым кодом завершения, и хотелось бы писать макровызов exit, а не exit 0. Перепишем макроопределение с использованием условного оператора
exit MACRO ret_code
IFNB <ret_code> mov al, ret_code
ELSE
6
mov al,0 ENDIF
DOS 4Ch ENDM
Оператор IFNB — IF Not Blank — указывает начало условно ассемблируемого блока, включаемого, если параметр ret_code не пустой. Для указания конца условно ассемблируемого блока используется директива ENDIF. Альтернативный код в условно ассемблируемом блоке начинается после ELSE. Параметр <ret_code> заключен в угловые скобки, т.к. должен здесь восприниматося как текстовая строка, а не как выражение, вычисляемое на этапе ассемблирования.
16.4. Включаемые файлы Чтобы не помещать накопленные нами полезные макроопределения в
начале каждого программного файла, поместим их в отдельный файл макроопределений.
файл macro.inc
CRLFT EQU 0Dh,0Ah,'$' DOS MACRO fun
mov ah,fun int 21h ENDM
message MACRO string
mov dx, OFFSET string DOS 09h
ENDM
exit MACRO return_code
IFNB <return_code> mov al,return_code
ELSE
mov al,0 ENDIF
DOS 4Ch ENDM
Окончательно программа first.asm принимает вид.
INCLUDE macro.inc
.MODEL small
.STACK 100h
.DATA
msg DB "Hello!",CRLFT
.CODE start: mov ax,@data
mov ds,ax message msg exit
END start
Она стала компактнее и нагляднее.
Упражнение. Изучите листинг и расширенный листинг для этого файла (для получения расширенного листинга используется ключ не /l, а /la).
7
16.5. Локальные метки.
Определим макрос do_dec MACRO
jcxz skip dec cx
skip:
ENDM
Если использовать такой макрос дважды, то после обработки текста программы препроцессором в программе будет содержаться две метки skip, что вызовет сообщение об ошибке (какое?).
Исправим макрос do_dec MACRO
LOCAL skip jcxz skip dec cx
skip:
ENDM
Метка skip объявлена локальной. Теперь при каждом использовании макроса будут генерироваться метки вида ??XXXX, где XXXX — 16-ричное число от 0 до FFFF.
Упражнение. Убедитесь в этом.
Директива LOCAL имеет формат: LOCAL список_меток.
Задача. Определите макрос stars n, который выводит на экран строку n звездочек (и заканчивает ее символами ВК и ПС). Напишите программу с использованием этого макроса. Программа должна выводить строки:
***
Hello
*****
Для вывода символа воспользуйтесь функцией 02 прерывания int 21h (в регистре DL — код выводимого символа). Для этой функции создайте макрос out_sym (в нем вызовите DOS 02h). Таким образом у вас получится три уровня вложенности макросов. Звездочки выводите в цикле.
16.6. Директива оптимизации переходов
Рассмотрим программный фрагмент: jz m
REPT 130 inc ax
ENDM
m:dec ax
Спомощью директивы REPT (REPeaT — повторять) в текст программы 130 раз помещается команда inc ax. Ее код занимает один байт. Всего получается блок из 130 байтов. Окружите этот фрагмент нужными директивами и командами и воспроизведите этот пример. При трансляции этой программы получим сообщение об ошибке
**Error** Relative jump out of range by 0003h bytes
(переход превышает допустимый диапазон на 3 байта)
Ранее мы познакомились с приемом, позволяющем избежать этой ошибки. Но в большой программе весьма утомительно изменять команды перехода.
8
Хотелось бы поручить это Ассемблеру — и это возможно. Поставьте первой строкой в файле с программой директиву JUMPS. В Turbo Debugger вы увидите сочетание команд jne/jmp (проверьте). Проблема решена!
Но не до конца. Замените REPT 130 на REPT 30. В отладчике Вы увидите jz m
nop nop nop inc ax
. . .
Так как jz m — переход "вперед", TASM при трансляции этой команды еще не знает, понадобится ли команда ближнего перехода или нет, и на всякий случай резервирует для нее три байта. Потом выясняется, что метка m в пределах достижимости для команды jz m, и эти три байта заполняются командами nop. Размер кода программы без нужды увеличен. Решение проблемы нам уже известно: используйте при трансляции ключ /m — многократные проходы по тексту программы. Тогда транслятор уберет паразитные команды.
16.7. Пример. Вычисление по формуле.
Переделаем второе задание — вычисление по формуле. (Интересно посмотреть, какие новые моменты мы можем привнести в программу в свете изученного материала). Во-первых, вычисление по формуле выделим в подпрограмму, во-вторых, вызов программы организуем с использованием макроса. Наконец, будем обрабатывать ошибку переполнения по сложению при вычислении знаменателя и ошибку при делении.
Немного изменим исходные данные для формулы. Теперь x не байт, а слово (чтобы проиллюстрировать переполнение при сложении).
У нас будет несколько тестовых наборов. Исходные данные — x,y,z. Результат — v и err. Если в err записан 0, то в v — правильный ответ. Но если в err помещено 1, то произошло переполнение при делении, а если 2 — переполнение при сложении.
Прерывание при ошибке деления принадлежит к категории исключений, поэтому, начиная с 286 процессора, в стек записывается не адрес следующей команды, а адрес команды, на которой произошло прерывание, чтобы можно было произвести рестарт ошибочной команды. Мы рестартовать idiv, конечно не будем, а скорректируем в стеке содержимое IP, чтобы перейти к команде, следующей за командой деления.
Так как мы напишем свой обработчик прерывания, освоим две функции DOS для установки вектора прерывания и его запоминания (чтобы восстанавливать его в конце программы).
получить вектор прерывания вход: AH = 35h
AL – номер прерывания выход: ES:BX — адрес ISR
установить вектор прерывания вход: AH = 25h
AL – номер прерывания
DS:DX — адрес ISR
9
выход: нет
Файл formula.asm INCLUDE macro.inc
; Макрос для вызова eval MACRO x,y,z,v,errn
LOCAL c,w mov ax,x mov bx,y mov cl,z call formula
jnc c ;; нет ошибки - на c
mov errn,al ;; запомнить код ошибки jmp short w
c: mov v,ax w:
ENDM
;вычисление по формуле
.MODEL small
.STACK 100h
.DATA
;старый вектор прерывания по ошибке деления ip_old DW ?
cs_old DW ?
;индикатор ошибки при делении
idiv_err DB ?
; первый набор
x1 |
DW 7FFFh ; x+3 даст переполнение при сложении |
y1 |
DW 0h |
z1 |
DB 0h |
v1 |
DW 0 |
err1 |
DB 0 |
; второй набор |
|
x2 |
DW -3h ; Знаменатель x+3 равен нулю |
y2 |
DW 1h |
z2 |
DB 2h |
v2 |
DW 0 |
err2 |
DB 0 |
; третий набор x3 DW 1h y3 DW -3h z3 DB 4h v3 DW 0 err3 DB 0
; четвѐртый набор
10
x4 DW 7Dh
y4 DW 6DB7h
z4 DB -6h
v4 DW 0 err4 DB 0
.CODE
;Подпрограмма вычисления по формуле
;v = (y(z-1)+1)/(x+3)+1
;вход : AX = x
;BX = y
;CL = z
;выход: CF=0 v = AX
;CF=1 AX = 1 - переполнение при делении
; |
AX = 2 - переполнение при сложении |
formula PROC |
|
push dx ; сохранение используемого регистра push ax
mov idiv_err,0 ; сброс индикатора ошибки mov al,cl ; z в AL
cbw |
|
dec ax |
; z-1 |
imul bx |
; y(z-1) |
add ax,1 |
|
adc dx,0 |
; y(z-1)+1 |
mov bx,ax ; сохранить младшее слово числителя |
|
pop ax |
; восстановить x |
add ax,3 |
; x+3 |
jo int_overflow |
|
xchg ax,bx ; восстановить числитель, знаменатель в BX |
|
idiv bx |
; (y(z-1)+1) |
cmp idiv_err,1 ; индикатор ошибки установлен? |
|
je idiv_overflow |
|
inc ax |
; окончательный результат |
clc |
; нормальное завершение |
pop dx |
; восстановить используемый регистр |
ret |
|
int_overflow: |
|
stc |
; ненормальное завершение |
mov ax,2 ; признак переполнения при сложении/вычитании |
|
pop dx |
|
ret |
|
idiv_overflow: |
|
stc |
|
mov ax,1; признак переполнения при делении |
|
pop dx |
|
ret formula ENDP
;