Упакованные BCD-числа хранятся по две цифры в байте в виде четырехбитовых групп, называемых тетрадами, причем каждая тетрада представляет собой двоичную комбинацию, соответствующую одной десятичной цифре, т.е. двоичное число в диапазоне 0000b – 1001b.
Неупакованное BCD-число содержит одну десятичную цифру в младшей тетраде байта, старшая тетрада должна быть нулевой, однако для команд сложения и вычитания содержимое старшей тетрады несущественно.
Команды десятичной коррекции упакованных чисел
Для упакованных десятичных чисел допустимы только операции сложения и вычитания. Каждая операция выполняется в два этапа.
На первом выполняется операция – сложение или вычитание (add, adc, sub, sbb) двух упакованных десятичных чисел, первое из которых должно находиться в регистре AL, на втором – десятичная коррекция результата в регистре AL (daa, das).
Рассмотрим подробнее одну из команд коррекции – daa, коррекция после сложения BCD-чисел.
После первого этапа – двоичного сложения правильных BCD-чисел возможно появление неправильного BCD-результата в двух ситуациях:
· получена недопустимая тетрада, т.е. тетрада, двоичный эквивалент которой больше 9;
· получена допустимая тетрада, но при сложении из нее возник двоичный перенос с весом 16, в то время как правильный вес единицы переноса должен быть равен 10.
Отметим, что перенос из младшей тетрады фиксируется флагом AF , а из старшей – CF.
Алгоритм выполнения команды daa состоит из двух шагов:
· если AF=1 или младшая тетрада регистра AL содержит запрещенную комбинацию, к содержимому AL прибавляется 06 и флаг AF устанавливается в 1;
· если CF=1 или старшая тетрада регистра AL содержит запрещенную комбинацию, к содержимому AL прибавляется 60h и флаг CF устанавливается в 1.
Например. Содержимое регистров AL=65h и BL=28h , что соответствует десятичным числам 65 и 28. Выполним их сложение
add AL, BL ; AL=8Dh, AF=0, CF=0, ZF=0
daa ; AL=93h, AF=0, CF=0, ZF=0
и вычитание
sub AL, BL ; AL=3Dh, AF=1, CF=0, ZF=0
das ; AL=37h, AF=1, CF=0, ZF=0
в комментариях показаны значения регистра AL и флагов после выполнения соответствующей команды.
Команды десятичной коррекции неупакованных чисел
Для неупакованных чисел или, как их еще называют ASCII-чисел, существуют аналогичные команды коррекции после сложения – aaa и вычитания – aas. Сложение и вычитание ASCII-чисел также выполняется в два этапа.
Кроме того, над ASCII-числами допустимо выполнение операций умножения и деления.
Умножение ASCII-чисел выполняется в два этапа:
· умножение одноразрядных сомножителей, представленных байтами, в которых младшие тетрады содержат десятичные цифры, а старшие тетрады – нулевые. Умножение выполняется командой mul, которая формирует в регистре AL двоичное произведение;
· коррекция результата с помощью команды aam, которая преобразует полученный результат в двухбайтовое произведение, находящееся в регистрах AH (старший десятичный разряд) и AL (младший разряд).
Деление также выполняется в два этапа, но в отличие от остальных команд коррекция выполняется не после, а перед выполнением операции:
· коррекция делимого с помощью команды aad, которая предполагает, что в регистрах AH и AL находится двухразрядное делимое, причем AH содержит цифру десятков, а AL – цифру единиц и обе старшие тетрады нулевые. Она формирует в AX соответствующее двоичное число;
· деление полученного в AX делимого на одноразрядный делитель, с получением частного в AL и остатка в AH.
ПРИМЕР ВЫПОЛНЕНИЯ РАБОТЫ
Написать программу сложения двух десятиразрядных неупакованных десятичных чисел.
Текст программы:
model SMALL
stack 100h
dataseg
Ask1 db 0Ah,0Dh,'Введите первое слагаемое (не более 10 цифр):$'
Ask2 db 0Ah,0Dh,'Введите второе слагаемое (не более 10 цифр):$'
Buf1 db 11
Len1 db ?
Opnd1 db 12 dup( ? )
Buf2 db 11
Len2 db ?
Opnd2 db 12 dup( ? )
ResT db 0Ah,0Dh,'Сумма '
Res db 12 dup(' '),'$'
AskCont db 0Ah,0Dh
db 'Завершить работу - Esc, продолжить - ЛЮБАЯ ÊËÀÂÈØÀ'
db '$'
codeseg
startupcode
push DS
pop ES ; ES <- DS
BEGIN:
;Ввод первого слагаемого
B1: lea DX, Ask1
mov AH, 09h
int 21h
lea DX, Buf1
mov AH, 0Ah
int 21h
cmp Len1, 0
je B1
;проверка 0-9 и очистка старш.тетрады
lea BX, Opnd1
xor CX, CX
mov CL, Len1
xor SI, SI
T1: mov AL, [BX][SI]
cmp AL, '0'
jb B1 ; ошибка
cmp AL, '9'
ja B1 ; ошибка
and AL, 0Fh
mov [BX][SI], AL
inc SI
loop T1
;прижать к правому краю
mov CL, Len1
cmp CL, 10
je E1
mov DI, 9
mov SI, CX
dec SI
P1: mov AL, [BX][SI]
mov [BX][DI], AL
dec DI
dec SI
loop P1
;обнулить лишнее
xor DI, DI
mov CL, 10
sub CL, Len1
N1: mov byte ptr [BX][DI], 0
inc DI
loop N1
E1:
;Ввод второго слагаемого
B2: lea DX, Ask2
mov AH, 09h
int 21h
lea DX, Buf2
mov AH, 0Ah
int 21h
cmp Len2, 0
je B2
;проверка 0-9 и очистка старш.тетрады
lea BX, Opnd2
xor CX, CX
mov CL, Len2
xor SI, SI
T2: mov AL, [BX][SI]
cmp AL, '0'
jb B2 ; ошибка
cmp AL, '9'
ja B2 ; ошибка
and AL, 0Fh
mov [BX][SI], AL
inc SI
loop T2
;прижать к правому краю
mov CL, Len2
cmp CL, 10
je E2
mov DI, 9
mov SI, CX
dec SI
P2: mov AL, [BX][SI]
mov [BX][DI], AL
dec DI
dec SI
loop P2
;обнулить лишнее
xor DI, DI
mov CL, 10
sub CL, Len2
N2: mov byte ptr [BX][DI], 0
inc DI
loop N2
E2:
;Сложение
mov CX, 10
clc
lea SI, Opnd1+9
lea DI, Opnd2+9
lea BX, Res+10
A1: mov AL, [SI]
adc AL, [DI]
aaa
mov [BX], AL
dec SI
dec DI
dec BX
loop A1
mov AL, 0
adc AL, 0
mov [BX], AL
;Преобразование результата в ASCII
mov CX, 11
A2: or byte ptr [BX], 30h
inc BX
loop A2
;Вывод результата
lea DX, ResT
mov AH, 09h
int 21h
;Запрос на продолжение работы
lea DX, AskCont
mov AH, 09h
int 21h
mov AH, 08h
int 21h
cmp AL, 27 ;ESC
je QUIT
jmp BEGIN
;Конец работы
QUIT: exitcode 0
end
ВАРИАНТЫ ЗАДАНИЙ
1. Имеются две группы заданий стандартной (варианты 1–5) и повышенной сложности (варианты 6–8), выберите самостоятельно любой вариант из какой–либо группы.
2. Введите два десятичных числа разрядностью не более 10 цифр, выполните преобразование в упакованный BCD-формат, сложите их и выведите результат.
3. Введите два десятичных числа разрядностью не более 10 цифр, выполните преобразование в упакованный BCD-формат, вычтете второе из первого и выведите результат.
4. Введите два десятичных числа, первое разрядностью не более 10 цифр, второе – из одной цифры выполните преобразование в неупакованный BCD-формат, перемножьте их и выведите результат.
5. Введите два десятичных числа разрядностью не более 10 цифр, выполните преобразование в неупакованный BCD-формат, вычтете второе из первого и выведите результат.
6. Введите два десятичных числа, первое разрядностью не более 10 цифр, второе – из одной цифры выполните преобразование в неупакованный BCD-формат, поделите первое на второе и выведите результат.
7. Напишите программу – калькулятор выполняющую действия + –, внутреннее представление чисел – упакованный BCD-формат.
8. Напишите программу – калькулятор выполняющую действия + – *, внутреннее представление чисел – неупакованный BCD-формат.
9. Напишите программу – калькулятор выполняющую действия + – /, внутреннее представление чисел – неупакованный BCD-формат.
КОНТРОЛЬНЫЕ ВОПРОСЫ
1. Чем отличаются упакованный и неупакованный BCD-форматы представления десятичных чисел ?
2. Что такое десятичная коррекция результата арифметической операции?
3. Почему используются различные команды десятичной коррекции для различных арифметических операций ?
4. Как организовать выполнение операций сложения и вычитания над многоразрядными операндами ?
5. Зачем нужны команды десятичной арифметики ?
6. Почему коррекция для деления выполняется перед операцией, а для остальных операций – после ?
Лабораторная работа |
ÏÎÄÏÐÎÃÐÀÌÌÛ |
ЦЕЛЬ РАБОТЫ
Цель настоящей работы – изучение приемов программирования с использованием подпрограмм.
ОСНОВНЫЕ СВЕДЕНИЯ
Описание подпрограмм
Описание подпрограммы в языке ассемблера имеет следующую структуру:
имя proc тип
. . .
операторы тела подпрограммы
. . .
ret
имя endp
Здесь «тип» - одно из слов NEAR (ближняя) или FAR (дальняя). Если тип не задан, по умолчанию принимается NEAR.
Процедура NEAR должна вызываться из того же сегмента кода, в котором она описана. Процедура FAR может вызываться из других сегментов, с другим значением регистра CS. Такие процедуры обычно используются как отдельные объектные модули или в составе библиотек.
Команда ret выполняет возврат из процедуры в вызывающую программу. Она не обязана быть последней по тексту процедуры, но является последней по порядку выполнения. Команда ret также имеет ближний и дальний варианты в зависимости от типа подпрограммы, внутри описания которой встретилась команда.
Допускается вложение описания подпрограммы внутрь описания другой подпрограммы.
В заголовке подпрограммы рекомендуется комментировать ее. Как правило, следует отразить следующие моменты: действие, выполняемое подпрограммой; входные и выходные параметры; ограничения и особенности подпрограммы.
Вызов подпрограмм
Вызов подпрограммы выполняется командой call. Вызов также бывает ближний или дальний. При ближнем вызове в стеке запоминается текущее значение регистра IP, используемое затем командой ret (ближней) для возврата в точку вызова. При дальнем вызове в стек заносится также значение сегментного регистра CS, что позволяет команде ret (дальней) выполнить возврат в другой сегмент.
Тип вызова определяется типом операнда команды. Если в качестве операнда указано имя подпрограммы, то тип FAR или NEAR выбирается в зависимости от описания подпрограммы. Если в качестве операнда используется слово или двойное слово памяти, то выполняется косвенный, соответственно ближний или дальний вызов подпрограммы по адресу, хранящемуся в памяти. При этом в двойном слове младшее слово содержит смещение, старшее слово - сегмент из адреса подпрограммы.
Например. Пусть в сегменте данных описаны переменные:
FADDR dd ?
NADDR dw ?
а в сегменте кода описаны подпрограммы:
FPROC proc FAR
. . .
FPROC endp
NPROC proc
. . .
NPROC endp
Рассмотрим следующие примеры команд вызова:
call FPROC ;Дальний прямой вызов п/п FPROC
call NPROC ;Ближний прямой вызов п/п NPROC
call FADDR ;Дальний прямой вызов п/п, чей адрес - в FADDR
call NADDR ;Ближний прямой вызов п/п, чей адрес - в NADDR
call BX ;Ближний косвенный вызов п/п, чей адрес - в рег.BX
call word ptr [BX] ;Ближний косвенный вызов п/п, чей адрес -
; в слове, адрес которого - в BX
call dword ptr [BX] ;Дальний косвенный вызов п/п, чей адрес -
; в двойном слове, адрес которого - в BX
Передача параметров
Программист имеет полную свободу в выборе способа передачи входных параметров в подпрограмму и выходных – из подпрограммы, важно лишь, чтобы обработка параметров в подпрограмме была согласована с заданием параметров в вызывающей программе.
Чаще всего применяется передача параметров через регистры или через стек.
При передаче через регистры программа перед вызовом заносит входные параметры в некоторые регистры процессора, а после возврата выбирает из регистров значения результатов.
При передаче через стек программа перед вызовом заносит параметры в стек с помощью команды push. Обычно при этом считается, что подпрограмма имеет только входные параметры (как функция в языке Си). Чтобы подпрограмма могла изменять значения параметров, следует передавать ей не сами значения, а адреса параметров.
Для доступа к параметрам, переданным в стеке, в начале подпрограммы обычно выполняются команды:
push BP
mov BP, SP
После этого можно адресовать величины в стеке, указывая их смещения относительно верхушки стека, адрес которой – в регистре BP. При подсчете смещения нужно учитывать, что команда call, как отмечалось выше, помещает в стек адрес возврата (одно или два слова). Удобно для адресации параметров описать соответствующую структуру данных.
Можно применять смешанные способы передачи параметров. В частности, для подпрограмм-функций удобно возвращать результат в регистре, даже если входные параметры получены в стеке.
Рассмотрим пример. Пусть подпрограмма типа near имеет два словных параметра, передаваемых через стек. В этом случае после вызова подпрограммы, сохранения и загрузки регистра BP (см. выше), стек будет выглядеть, как показано ниже:
BP | Ü SP сохраненное значение BP |
IP | адрес возврата |
2-й параметр | параметр, занесенный в стек вторым |
1-й параметр | параметр, занесенный в стек первым |
. . . |
Если описать следующую структуру:
__arg struc
__saveBP dw ?
__retAddr dw ?
__Param2 dw ?
__Param1 dw ?
__arg ends,
то доступ к параметрам можно осуществить с помощью команд:
mov AX, __Param1[BP]; загрузить в AX значение первого параметра
mov BX, __Param2[BP]; загрузить в BX значение второго параметра
Для облегчения чистки стека от переданных параметров используется разновидность команды ret с операндом – числом байтов, которые нужно убрать из стека сразу после возврата. Это позволяет вызывающей программе не заботиться об удалении параметров из стека. Для нашего примера команда возврата из подпрограммы может выглядеть следующим образом:
ret 4
Сохранение регистров
Каждая подпрограмма должна либо сохранять значения всех регистров процессора (кроме тех, которые используются для возврата результатов), либо, в крайнем случае, в описании подпрограммы должно быть четко указано, какие регистры она портит. Для сохранения регистров используется стек. Команды push служат для помещения регистров в стек, а pop – для их восстановления перед возвратом из подпрограммы. Сохранение регистров должно выполняться после загрузки BP (см. предыдущий параграф).
Локальные переменные
Переменные, размещенные в сегменте данных, являются статическими (аналогично переменным с классом static в Си). Конечно, их можно рассматривать как локальные переменные подпрограмм, обеспечив локализацию области действия с помощью директивы locals (см. ниже). Однако такое статическое распределение памяти под локальные переменные не соответствует понятию локальных переменных в блочных языках типа Pascal или C, поскольку время существования таких переменных – время существования программы. Для того чтобы решить данную проблему, т.е. обеспечить динамическое распределение памяти под локальные переменные, следует выделять для них память в стеке (как это делается в Pascal или C).
Предположим, что в подпрограмме должно быть две локальные переменные длиной в слово. Чтобы обеспечить выделение памяти, для них перед командами сохранения регистров следует добавить команду:
sub SP, 4
которая резервирует в стеке два слова.
После выполнения этой команды стек будет выглядеть следующим образом:
1-я лок.пер. | Ü SP |
2-я лок.пер. | |
BP | сохраненное значение BP |
IP | адрес возврата |
2-й параметр | параметр, занесенный в стек вторым |
1-й параметр | параметр, занесенный в стек первым |
. . . |
И, если определить структуру:
__locvars struc
__var1 dw ?
__var2 dw ?
__locvars ends,
то доступ к локальным переменным можно осуществить с помощью команд:
mov AX, __var1[BP-4];загрузить в AX значение 1-й локальной переменной
mov BX, __var2[BP-4];загрузить в BX значение 2-й локальной переменной
Чистка стека от локальных переменных должна выполняться после восстановления сохраненных регистров, это можно сделать с помощью команды:
add SP, 4
или
mov SP, BP