s
Sesiya.ru

16.1. Директивы повторения Таких директив три: REPT, IRP, IRPC

Информация о работе

Тема
16.1. Директивы повторения Таких директив три: REPT, IRP, IRPC
Тип Лекции
Предмет Информатика
Количество страниц 12
Язык работы Русский язык
Дата загрузки 2014-06-12 23:13:36
Размер файла 21.59 кб
Количество скачиваний 11
Скидка 15%

Поможем подготовить работу любой сложности

Заполнение заявки не обязывает Вас к заказу


Скачать файл с работой

Помогла работа? Поделись ссылкой

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
В отладчике перейдите в панель кода и после 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
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
Но проще так:
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
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
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).

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 байта)
Ранее мы познакомились с приемом, позволяющем избежать этой ошибки. Но в большой программе весьма утомительно изменять команды перехода. Хотелось бы поручить это Ассемблеру — и это возможно. Поставьте первой строкой в файле с программой директиву 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
выход: нет

Файл 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

; четвёртый набор
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
;
; обработчик прерывания при ошибке деления
divide_overflow PROC
sti ; Разрешить внешние прерывания
mov idiv_err,1 ; Установить индикатор ошибки
mov bp,sp ; В BP - адрес IP, сохраненного в стеке,
; это адрес команды idiv bx (код F7 FB занимает два байта)
add word ptr [bp], 2 ; теперь IP показывает на следующую команду
iret
divide_overflow ENDP

start: mov ax,@data
mov ds,ax
; Получить вектор прерывания для ошибки при делении (тип 0)
mov al,0
DOS 35h
mov ip_old,bx ; Сохранение
mov cs_old,es ; вектора
; Установить новый вектор прерывания
mov bx,ds ; сохранить DS
mov al,0
mov dx, SEG divide_overflow
mov ds,dx
mov dx, OFFSET divide_overflow
DOS 25h
mov ds,bx; Восстановить DS
; Первый набор
eval x1,y1,z1,v1,err1
; Второй набор
eval x2,y2,z2,v2,err2
; Третий набор
eval x3,y3,z3,v3,err3
; Четвёртый набор
eval x4,y4,z4,v4,err4
; Восстановить вектор прерывания
mov al,0
mov dx,ip_old
mov ds,cs_old
DOS 25h
;
exit
END start

Возникает вопрос, как в TD отлаживать процедуру обработки прерывания по ошибке деления? Если выполнять программу пошагово (нажимая F7), то в эту процедуру мы не попадем. В процедуре надо сделать точку останова (Breakpoint). Поставьте курсор на команду sti и нажмите клавишу F2. Строка будет выделена красным цветом. (Повторное нажатие на F2 отменит точку останова.) Запустите программу на выполнение (F9). Произойдет останов на команде sti. Далее можно изучать работу программы с помощью пошагового выполнения. (Впрочем, можно было обойтись и "старыми запасами": поставить курсор на sti и нажать F4.)

Макросредства языка Си.
…….

© Copyright 2012-2020, Все права защищены.