Файл: О.А.Калашников. Ассемблер Это Просто. Учимся программировать.pdf

ВУЗ: Не указан

Категория: Книга

Дисциплина: Программирование

Добавлен: 16.02.2019

Просмотров: 29205

Скачиваний: 1689

ВНИМАНИЕ! Если данный файл нарушает Ваши авторские права, то обязательно сообщите нам.
background image

 

Часть 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 
... 


background image

Глава 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. 


background image

 

Часть 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 


background image

Глава 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). 


background image

 

Часть 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). 

Обратите внимание на скорость вывода символов при запуске программы. 
Еще раз обращаем ваше внимание на то, что программы желательно и необхо-

димо печатать самостоятельно, чтобы быстрее привыкнуть к операторам и освоить 
ассемблер. Мы уже не один раз заостряли на этом внимание, т. к. это действитель-
но довольно важно при изучении любого языка, и особенно ассемблера.