Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Финогенов-основы_языка_ассемблера.doc
Скачиваний:
46
Добавлен:
17.09.2019
Размер:
3.35 Mб
Скачать

Глава 3

Команды и алгоритмы

125

дующую за командой loop. Поэтому после нормального выхода из цикла содержимое СХ всегда равно 0.

Другое обстоятельство связано с кодированием команды loop. В ее коде под смещение к точке перехода отводится всего 1 байт. Поскольку смещение должно являться величиной со знаком, максимальное расстояние, на кото­рое можно передать управление командой loop, составляет от -128 до +127 байт (хотя довольно трудно представить себе цикл, в котором переход осу­ществляется вперед). Другими словами, тело цикла ограничивается всего 128 байтами. Если циклически повторяемый, фрагмент программы имеет боль­шую длину, цикл придется организовать другим, более сложным способом;

Организация длинного цикла

mov СХ, 10000 ;Счетчик цикла

fill:

; Метка начала цикла

;Тсло длинного цикла

dec cmp

СХ

сх,о

finish

;Дскремент счетчика цикла

;Отработано заданное число

; шагов'

;Да, на метку продолжения

; про граммы

jmp fill

;Нет, на начало цикла

;Продолжение программы

finish:

В этом, весьма типичном фрагменте мы «вручную» уменьшаем содер­жимое счетчика цикла и сравниваем полученное значение с 0. Если СХ = О, это значит, что в цикле выполнено заданное число шагов, и командой условного перехода je осущсстшмется переход на продолжение програм­мы (метка finish). Если СХ еще не равно нулю, командой безусловного перехода jmp осуществляется возврат в начало цикла. Как было показано в гл. 2, команда jmp позволяет перейти в любую точку сегмента, и огра­ничение на размер тела цикла снимается.

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

mov СХ.2000 ;Счетчик внешнего цикла

outer: push СХ ;Сохраним его в стеке

mov CX,0 ;Счетчик внутреннего цикла

inner: loop inner ;loop внутреннего цикла

pop СХ ;Восстановим внешний счетчик

loop outer ;loop внешнего цикла

Программные задержки удобно использовать при отладке программ, чтобы замедлить их работу и успеть рассмотреть их частичные результаты; иногда программные задержки позволяют синхронизовать работу аппара­туры, подключенной к компьютеру, если скорость отработки аппаратурой посыпаемых в нее из компьютера команд меньше скорости процессора. ~

В приведенном выше фрагменте внешний цикл выполняется 2000 раз; внутренний — 65536 раз. При счете числа шагов внутреннего цикла ис­пользуется явление оборачивания, которое уже упоминалось ранее. На­чальное значение в регистре СХ равно нулю; после выполнения тела цик­ла 1 раз команда loop уменьшает содержимое СХ на 1, что дает число FFFFh (которое можно рассматривать, как -1). В результате цикл повто­ряется еще 65535 раз, а в сумме — точно 64 К шагов.

Команда loop внутреннего цикла передает управление на саму себя, т.е. тело внутреннего цикла состоит из единственной команды loop. В этом нет ничего незаконного. Любая команда, в том числе и loop, требует ка­кого-то времени для своего выполнения, и повторение 64 К раз команды loop дает некоторую временную задержку (на современных процессорах порядка тысячной доли секунды).

Перейдем теперь к рассмотрению команд условных переходов.

В приведенном выше фрагменте для реализации длинного цикла ис­пользовалась команда условного перехода по равенству je. В системе ко­манд МП 86 имеется свыше трех десятков команд условных переходов, позволяющих осуществлять переходы при наличии разнообразных усло­вий: равенства, неравенства, положительности или отрицательности ре­зультата и проч. При выполнении всех этих команд процессор анализиру­ет содержимое регистра флагов и осуществляет (или не осуществляет) переход на указанную метку в зависимости от состояния отдельных фла­гов или их комбинаций. Поскольку на состояние регистра флагов влияют многие команды процессора, командами условных переходов можно пользоваться не только после команд сравнения или анализа, но и после многих других команд, если внимательно изучить влияние этих команд на флаги процессора. Приведем несколько абстрактных примеров.

cmp

AX,BX

je

equal

cmp

SI, mem

jne

notequ

hit

21h

jc

syserr

or

BX,BX

jz

zero

in

AL,DX

test

AL,80h

je

inpt

test

AX, 7

jne

found

;Сравнение двух регистров ;Переход, если АХ=ВХ ;Сравнение регистра и ячейки памяти ;Переход, если SlOmem ;Вызов DOS

;Переход, если была ошибка ;и флаг CF = 1 ;Анализ ВХ ; Переход, если ВХ=0

inpt: in AL,DX ;Ввод данного из устройства

;Анализ бита 7 в данном ;Ввод до тех пор, пока бит ;бит 7=0(ожидание установки бита 7) ;Анализ битов О, I, 2 в АХ ;Переход, если хотя бы 1 бит ;из них установлен

test DI,OFh ;Анализ битов 0...3 в DI

jz reset ;Переход, если все они сброшены

126

127

Глава j

В гл. 2 отмечаюсь, что двоичные числа, записываемые в регистры про­цессора или ячейки памяти, можно рассматривать, либо как числа суще-ственно положительные, т.е. числа без знака, либо как числа со знаком. Например, адреса ячеек, разумеется, не могут быть отрицательными. По­этому число FFFFh, если по смыслу программы оно является адресом, обозначает 65535. Если, однако, то же число FFFFU получилось в ариф­метической операции вычитания 2 из 1, то его надо рассматривать, как — 1. Точно так же понятие знака бессмысленно по отношению к кодам сим­волов, которые с равным успехом могут принимать любое значение из диапазона 0...255. С другой стороны, мы можем условно считать, что коды символов первой половины таблицы ASCII положительны, а коды вто­рой половины таблицы (у них установлен старший бит) отрицательны, и использовать для обработки символов команды, чувствительные к знаку.

В составе команд условных переходов имеются две группы команд для сравнения чисел без знака (это команды ja, jae, jb, jbe, jna, jnae, jnb и jnbe) и чисел со знаком (jg> Jgc, jl, jle, jug, jiige, jnl и jnle). В аббревиатурах этих команд для сравнения чисел без знака используются слова above (выше) и below (ниже), а для чисел со знаком — слова greater (больше) и Less (меньше).

Разница между теми и другими командами условных переходов зак­лючается в том, что команды для чисел со знаком рассматривают поня­тия «больше-меньше* применительно к числовой оси -32К...О...+32К, а команды для чисел без знака — применительно к числовой оси 0...64К. Поэтому для первых команд число 7FFFh (+32767) больше числа SOOOh (-32768), а для вторых число 7FFFU (32767) меньше числа 8000U (32768). Аналогично, команды для чисел со знаком считают, что 0 больше, чем FFFFh (-1), а команды для чисел без знака — меньше.

Рассмотрим пример использования команд условных переходов для обработки символов. Пусть мы вводим с клавиатуры некоторую строку сим­волов (например, имя файла), и хотим, чтобы в программе эта строка была записана прописными буквами, независимо от того, какие буквы исполь­зовались при ее вводе. Между прочим, при вводе с клавиатуры команд DOS система всегда выполняет эту операцию, поэтом}' и команды, и ключи, и имена файлов можно вводить как прописными, так и строчными буквами — DOS во всех случаях преобразует все буквы в прописные.

Пример 3-5. Сравнение чисел без знака

code segment ;

assume cs:code,ds:data \'\

main proc i--\

mov AX,data Инициализируем :

mov DS,AX ;регистр DS •)•-

;Выведем служебное сообщение |v

mov AH,09h ;Функция вывода :;,

mov DX,oflset msg ;Адрес сообщения, ,h

int 2 Hi \*

;Поставим запрос к DOS на ввод строки

г

Команды а алгоритмы

mov mov mov mov int mov

AH,3Fh BX,0 CX,80 DX,offset buf 21h action, AX

;Функция ввода Дескриптор клавиатуры ;Ввод максимум 80 байт ;Адрес буфера ввода

;Фактически введено

;Превратим строчные русские буквы в прописные

mov

CX,actlen

mov

SI,0

filter: mov

AL,buf[SI]

cmp

AL,'a'

jb

noletter

cmp

AL/я1

ja

noletter

cmp

AL, 'n'

ja

more

sub

AL,20h

jmp

store

more: cmp

AL, 'p'

jb

noletter

sub

AL,50h

store: mov

buf[SI],AL

noletter:

inc SI

loop filter

;Длина введенной строки

;Указатель в буфере

;Возьмем символ

;Меньше 'а'?

;Да, не преобразовывать

;Болыпе 'я'?

;Да, не преобразовывать

; Больше 'п1?

;Да, на дальнейшую проверку

;'а'...'п'. Преобразуем в прописную

;На сохранение в буфере

;Меньше 'р1 (псевдографика)?

;>'п', <'р'. Не изменять

;'р'...'я'. Преобразуем в прописную

;Отправим назад в buf

;Сместим указатель

;Цикл по всем символам

;Функция вывода ;Дескриптор экрана ;Длина сообщения ;Адрес сообщения

;Остановим программу

;в ожидании нажатия клавиши

AH,40h BX,1

CX,actlen DX,oflset buf 21h AH ,01 2 111

;Выведем результат преобразования на экран для контроля

mov

mov

mov

mov

int

mov

int ;3авершим программу

mov AX,4COOh

; Буфер ввода

int 2 In main endp code ends data segment msg db 'Вводите! S' buf db 80 dup (' ') actlendw 0 data ends stk segment stack dw 128 dup (') stk ends end main

128

Глава j

Цдчанды и алгоритмы

129

В начале программы на экран выводится служебное сообщение «Вво­дите! «, которое служит запросом программы, адресованным пользовате­лю. Далее с помощью функции DOS 3Fh выполняется ввод строки текста с клавиатуры. Функция 3Fh может вводить данные из разных устройств — файлов, последовательного порта, клавиатуры. Различные устройства иден­тифицируются их дескрипторами. При работе с файлами дескриптор каж­дого файла создается системой в процессе операции открытия или созда­ния этого файла, а для стандартных устройств — клавиатуры, экрана, принтера и последовательного порта действуют дескрипторы, закрепляе­мые за этими устройствами при загрузке системы. Для ввода с клавиатуры используется дескриптор 0, для вывода на экран дескриптор 1.

При вызове функции 3Fh в регистр ВХ следует занести требуемый дескриптор, в регистр DX — адрес области в программе, выделенной для приема вводимых с клавиатуры символов, а в регистр СХ — максималь­ное число вводимых символов. Мы считаем, что пользователь не будет вводить более 80 символов. Можно ввести и меньше; в любом случае ввод строки следует завершить нажатием клавиши <Enter>. Функция 3Fh, от­работав, вернет в регистре АХ решхьное число введенных символов (вклю­чая коды 13 и 10, образуемые при нажатии клавиши <Enter>). В примере 3.5 число введенных символов сохраняется в ячейке actlen с целью ис­пользования далее по ходу программы.

Далее в цикле из actlen шагов выполняется анализ каждого введенно­го символа путем сравнения с границами диапазонов строчных русских букв. Русские строчные буквы размещаются в двух диапазонах кодов ASCII (а...п и р...с), причем для преобразования в прописные букв первого диа­пазона их код следует уменьшать на 20h, а для преобразования букв вто­рого диапазона — на 50h. Поэтому анализ проводится с помощью четырех команд сравнения стр и соответствующих команд условных переходов. Модифицированный символ записывается на то же место в буфере buf.

После завершения анализа и преобразования введенных символов, выполняется контрольный вывод содержимого buf на экран. Поскольку мы заранее не знаем, сколько символов будет введено, вывод на экран осуществляется функцией 40h, среди параметров которой указывается число выводимых символов. Так же, как и в случае функции ввода 3Fh, для функции вывода 40h в регистре ВХ необходимо указать дескриптор устройства ввода, в данном случае экрана, а в регистре DX — адрес выво­димой строки.

Коды символов являются числами без знака, и использование в дан­ном случае команд условных переходов для чисел без знака представляет­ся логичным и даже единственно возможным. Если, однако, внимательно рассмотреть понятия больше-меньше для чисел со знаком и без знака, то легко увидеть, что пока мы сравниваем друг с другом только «положи­тельные» или только «отрицательные» числа, команда ja эквивалентна команде jg, а команда jb эквивалентна команде J1. Однако при сравнении, например, кодов цифр с кодами русских букв, правильный результат можно получить лишь при использовании команд переходов для чисел

без знака. Впрочем, всегда нагляднее и надежнее использовать те коман­ды, которые соответствуют существу рассматриваемых данных, даже если такой же правильный результат получится и при использовании «непра­вильных» команд.

Более отчетливо разница между числами со знаком и без знака прояв­ляется при использовании арифметических операций, например, опера­ций умножения или деления. Здесь для чисел со знаком и чисел без знака предусмотрены отдельные команды:

inul — команда умножения чисел без знака; imul — команда умножения чисел со знаком; div — команда деления чисел без знака; idiv — команда деления чисел со знаком.

Поясним различия этих команд на формальных примерах.

;Умножение положительных чисел со знаком

mov AL,5 ;Первый сомножитель=5

mov BL.7 ;Второй сомножитель=7

mul BL ;AX=0023h=35

mov AL,5 ;Первый сомножитель=5

mov BL,7 ;Второй сомножитель=7

imul BL ;AX=0023h=35

Обе команды, mul и imul, дают в данном случае одинаковый резуль­тат, так как положительные числа со знаком совпадают с числами без знака. Не так обстоит дело при умножении отрицательных чисел.

;Умножение отрицательных чисел со знаком

mov AL,OFCh ;Первый сомножитель=252

mov BL,4 ;Второй сомножитель=4

mul BL ;AX=03FOh= 1008

mov AL,OFCh ;Первый сомножитель=-4

mov BL,4 ;Второй сомножитель=4

imul BL ;AX=FFFO=-16

Здесь действие команд mul и imul над одними и теми же операндами дает разные результаты. В первом примере число без знака FCh, которое интерпретируется, как 252, умножается на 4, давая в результате число без знака 3FO, т.е. 1008. Во втором примере то же число FCh рассматривается, как число со знаком. В этом случае оно составляет -4. Умножение на 4 дает FFFOh.T.e.-16.

130