Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Assembler / P16

.pdf
Скачиваний:
60
Добавлен:
02.06.2015
Размер:
387.42 Кб
Скачать

1

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

;

Соседние файлы в папке Assembler