Файл: О.А.Калашников. Ассемблер Это Просто. Учимся программировать.pdf
Добавлен: 16.02.2019
Просмотров: 29205
Скачиваний: 1689
Часть II. Усложняем задачи
42
4.2. Создание циклов
Что такое цикл? Допустим, нам нужно выполнить некоторый код программы
несколько раз. Возьмем, к примеру, вывод строки функцией
09h
прерывания
21h
(листинг 4.1).
Листинг 4.1. Выполнение однотипных команд несколько раз
...
mov ah,9
mov dx,offset Str
int 21h
mov ah,9
mov dx,offset Str
int 21h
mov ah,9
mov dx,offset Str
int 21h
...
Этот участок кода выведет 3 раза на экран некую строку с названием
Str
. Код
получается громоздким, читать его неудобно. Размер программы разрастается...
Для выполнения подобных примеров используется оператор
loop
(табл. 4.2).
Таблица 4.2. Оператор
loop
Команда
Перевод
Назначение
Процессор
loop метка
loop
— петля
Организация циклов
8086
Количество повторов задается в регистре
cx
(счетчик). В листинге 4.2 показано, как
можно использовать этот оператор на практике (изменим программу из листинга 4.1).
Листинг 4.2. Организация цикла
...
(01) mov cx,3
(02) Label_1:
(03) mov ah,9
(04) mov dx,offset Str
(05) int 21h
(06) loop Label_1
...
Глава 4. Создание циклов
43
В строке (01) загружаем в
cx
количество повторов, при этом отсчет будет идти
в обратном порядке — от 3 до 0. В строке (02) создаем метку (от англ. Label —
метка). Далее (строки (03)—(05)) выводим сообщение.
В строке (06) оператор
loop
уменьшает на единицу содержимое регистра
cx
и,
если число в нем не стало равно нулю, переходит на метку
Label_1
(02). Итого
строка будет выведена на экран три раза. Когда программа перейдет на строку (07),
регистр
cx
будет равен нулю. В результате код программы уменьшается почти
в три раза по сравнению с примером из листинга 4.1. Строки (02)—(06) являются
телом цикла.
4.2.1. Пример высокоуровневой оптимизации
Однако, несмотря на то, что код программы уменьшается, время выполнения
увеличивается, т. к. процессору приходится обрабатывать не только строки (03)—
(05), но еще и оператор
loop
. В данном случае можно уменьшить тело цикла, т. к.
функция
09
прерывания
21h
не изменяет регистров. В листинге 4.3 приведен опти-
мизированный вариант программы из листинга 4.2.
Листинг 4.3. Оптимизация кода
...
(01) mov ah,9
(02) mov dx,offset Str
(03) mov cx,3
(04) Label_1:
(05) int 21h
(06) loop Label_1
...
Здесь видно, что загрузка номера функции и смещения строки вынесена за преде-
лы тела цикла. Таким образом, хоть программа и не уменьшится в размере, но будет
работать почти в три раза быстрее. Это называется высокоуровневой оптимизацией
программ, которую будем постепенно рассматривать в последующих главах.
На рис. 4.2 показано состояние регистров после выполнения цикла, приведенно-
го в листинге 4.3.
Забегая немного вперед, отметим, что сама метка
Label_1
, как видно на рис. 4.1,
в отладчике нигде не отображается, а по адресу, на который переходит
loop
, нахо-
дится инструкция
int 21h
. Следовательно, метки памяти не занимают. Они необ-
ходимы только в процессе ассемблирования программы, чтобы ассемблер-
программа могла подставить реальное смещение кода.
При подобных оптимизациях следует иметь в виду, что далеко не все функции не
изменяют регистры в процессе работы. Например, известная нам функция
10h
пре-
рывания
16h
, которая возвращает в
ax
код нажатой пользователем клавиши, исклю-
чает возможность помещения ее в цикл, подобный приведенному в листинге 4.3.
Часть II. Усложняем задачи
44
Рис. 4.2. Состояние регистров после выполнения цикла
4.3. Условный и безусловный переходы
Условный переход — это передача управления по другому адресу или на указан-
ную метку, если выполняется определенное условие. Примером условного перехо-
да может служить описанная ранее команда
loop
, которая передает управление на
указанную метку только в случае, если регистр
cx
не равен нулю. Если же
cx
со-
держит ноль, то это является сигналом прекращения выполнения цикла и продол-
жения работы программы, т. е. в этом случае переход на метку не производится.
Безусловный переход — это передача управления по другому адресу, на указанную
метку в программе не зависимо от того, выполняется ли какое-либо условие или нет.
Пример безусловного перехода и реализующая его команда приведены далее.
В табл. 4.3 приведено описание команды, выполняющей безусловный переход.
Таблица 4.3. Оператор
jmp
Команда
Перевод
Назначение
Процессор
jmp метка
Jump
— прыжок (в данном
случае — переход)
Безусловный переход
8086
Команда
jmp
просто переходит на указанную метку в программе без каких бы то
ни было условий (листинг 4.4).
Листинг 4.4. Безусловный переход
...
(01) mov ah,9
(02) mov dx,offset Str
Глава 4. Создание циклов
45
(03) int 21h
(04) jmp Label_2
(05)
(06) add cx,12
(07) dec cx
(08) Label_2:
(09) int 20h
...
В результате строки (05)—(07) выполняться не будут. Программа выведет со-
общение на экран, а затем инструкция
jmp
заставит программу перейти на строку
(08), после чего последняя завершится.
4.3.1. Пример низкоуровневой оптимизации
С помощью оператора
dec
можно создавать циклы, которые будут работать бы-
стрее, чем с классическим применением оператора
loop
. Пример из листинга 4.5
будет работать так же, как программа из листинга 4.2, только чуть-чуть быстрее (на
некоторых более старых процессорах).
Листинг 4.5. Пример низкоуровневой оптимизации
...
(01) mov cx,3
(02) Label_1:
(03) mov ah,9
(04) mov dx,offset Str
(05) int 21h
(06) dec cx
(07) jnz Label_1
...
Не обращайте внимания на строку (07). Мы ее рассмотрим позже. Этот пример
приведен для того, чтобы показать, что один и тот же прием в ассемблере можно
выполнить разными операторами (равно, как и в языках высокого уровня). И чем
лучше программист владеет этими операторами, тем компактнее и быстрее про-
грамма будет работать. Поэтому и получается, что разные программисты пишут на
одном языке, но скорости и размеры программ различаются. В процессе обучения
мы также будем учиться оптимизировать программы.
4.4. Программа для практики
Усовершенствуем программу из главы 3, которая выводила в левый верхний
угол "рожицу" прямым отображением в видеобуфер (листинг 4.6, \004\prog04.asm).
Часть II. Усложняем задачи
46
Листинг 4.6. Программа для практики
(01) CSEG segment
(02) org 100h
(03) Begin:
(04) mov ax,0B800h
(05) mov es,ax
(06) mov di,0
(07) mov al,1
(08) mov ah,31
(09) mov cx,2000
(10)
(11) Next_face:
(12) mov es:[di],ax
(13) add di,2
(14) loop Next_face
(15)
(16) mov ah,10h
(17) int 16h
(18) int 20h
(19) CSEG ends
(20) end Begin
Прежде чем читать описание программы, попробуйте сами разобраться, что
в итоге получится. Поверьте, это принесет вам пользу.
4.4.1. Принцип работы программы
Строки с (01) по (10) и с (15) по (20) вы уже знаете. Рассматриваем только но-
вое. Строка (11) — это метка, "голова" нашего цикла, строка (14) — "хвост". Все,
что находится в пределах строк (10)—(14), является телом цикла. Сам же цикл бу-
дет повторяться 2000 раз, для чего мы и заносим в
cx
число 2000 (строка (08)).
В строке (12) записываем в видеобуфер по адресу
0B800:DI
число, находящееся
в
ax
(атрибут + символ). Итак, первый символ занесли. Далее увеличиваем регистр
di
на 2, для того чтобы перейти к адресу следующего символа.
Почему на 2? Дело в том, что в видеобуфере один символ занимает 2 байта:
сам символ и его атрибут. Так как символ у нас находится в регистре
al
, а атрибут
в
ah
, и мы загрузили уже эти два байта в строке (12), то и увеличиваем
di
(смеще-
ние) на 2.
di
теперь указывает на адрес следующего символа. Осталось уменьшить
счетчик
cx
на 1 и повторить. Что мы, собственно, и делаем в строке (14).
Обратите внимание на скорость вывода символов при запуске программы.
Еще раз обращаем ваше внимание на то, что программы желательно и необхо-
димо печатать самостоятельно, чтобы быстрее привыкнуть к операторам и освоить
ассемблер. Мы уже не один раз заостряли на этом внимание, т. к. это действитель-
но довольно важно при изучении любого языка, и особенно ассемблера.