Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Лекции / Gl11.doc
Скачиваний:
25
Добавлен:
20.05.2014
Размер:
97.28 Кб
Скачать

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).

11.7. Локальные метки.

Определим макрос

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список_меток.

Задача. Определите макросstarsn, который выводит на экран строкуnзвездочек (и заканчивает ее символами ВК и ПС). Напишите программу с использованием этого макроса. Программа должна выводить строки:

***

Hello

*****

Для вывода символа воспользуйтесь функцией 02 прерывания int21h(в регистреDL— код выводимого символа). Для этой функции создайте макросout_sym(в нем вызовитеDOS02h). Таким образом у вас получится три уровня вложенности макросов. Звездочки выводите в цикле.

11.8. Директива оптимизации переходов

Рассмотрим программный фрагмент:

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— многократные проходы по тексту программы. Тогда транслятор уберет паразитные команды.

11.9. Пример. Вычисление по формуле.

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

Немного изменим исходные данные для формулы. Теперь 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.)

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