Циклы


Одним из видов конструкций в программе, которые можно пост-роить с помощью условных переходов, являются циклы. Цикл — это просто-напросто блок кода, завершающийся условным переходом, бла-годаря чему данных блок может выполняться повторно до достижения условия завершения. Возможно, вам уже знакомы такие конструкции циклов, как for и while в языке Си, while и repeat в Паскале и FOR в Бейсике.

Для чего используются циклы? Они служат для работы с масси-вами, проверки состояния портов ввода-вывода до получения опреде-ленного состояния, очистки блоков памяти, чтения строк с клавиа-туры и вывода их на экран и т.д. Циклы — это основное средство, которое используется для выполнения повторяющихся действий. Поэ-тому используются они довольно часто, настолько часто, что в на-боре инструкций процессора 8086 предусмотрено фактически несколь-ко инструкций циклов: LOOP, LOOPNE, LOOPE и JCXZ.

Давайте рассмотрим сначала инструкцию LOOP. Предположим, мы хотим вывести 17 символов текстовой строки TestString. Это можно сделать следующим образом:

DATA

TestString DB ‘Это проверка! …’

.

.

.

.CODE

.

.

.

mov cx,17

mov bx,OFFSET TestString

PrintStringLoop:

mov dl,[bx] ; получить следующий

; символ

inc bx ; ссылка на следующий

; символ

mov ah,2 ; функция DOS вывода на

; экран

int 21h ; вызвать DOS для вывода

; символа

dec cx ; уменьшить счетчик длины

; строки

jnz PrintStringLoop ; обработать следующий

; символ, если он имеется

.

.

.

Есть, однако, лучший способ. Возможно, вы помните, что ранее мы уже упоминали о том, что регистр CX весьма полезно бывает ис-пользовать для организации циклов. Инструкция:

loop PrintStringLoop

делает то же, что и инструкции:

dec cx

jnz PrintStringLoop

однако выполняется она быстрее и занимает на один байт меньше. Всякий раз, когда вам нужно организовать цикл, пока значение счетчика не станет равным 0, запишите начальное значение счетчика в регистр CX и используйте инструкцию LOOP.

Как же строятся циклы с более сложным условием завершения, чем обратный отсчет значения счетчика? Для таких случаев предус-мотрены инструкции LOOPE и LOOPNE.

Инструкция LOOPE работает также, как инструкция LOOP, только цикл при ее выполнении будет завершаться (то есть перестанут вы-полняться переходы), если регистр CX примет значение 0 или флаг нуля будет установлен в значение 1 (нужно помнить о том, что флаг нуля устанавливается в значение 1, если результат последней ариф-метической операции был нулевым или два операнда в последней опе-рации сравнения не совпадали). Аналогично, инструкция LOOPNE за-вершает выполнение цикла, если регистр CX принял значение 0 или флаг нуля сброшен (имеет нулевое значение).

Предположим, вы хотите повторять цикл, сохраняя коды нажатых клавиш, пока не будет нажата клавиша ENTER или не будет накоплено 128 символов. Для выполнения такой работы можно написать такую программу (где используется инструкция LOOPNE):

.

.

.

.DATA

KeyBuffer DB 128 DUP (?)

.

.

.

.CODE

.

.

.

mov cx,128

mov bx,OFFSET KeyBuffer

KeyLoop:

mov ah,1 ; функция DOS ввода с

; клавиатуры

int 21h ; считать следующую

; клавишу

mov [bx],al ; сохранить ее

inc bx ; установить указатель

; для следующей клавиши

cmp al,0dh ; это клавиша ENTER?

loopne KeyLoop ; если нет, то получить

; следующую клавишу, пока

; мы не достигнем максимально-

; го числа клавиш

.

.

.

Инструкция LOOPE известна также, как инструкция LOOPZ, инструкция LOOPNE — как инструкция LOOPNZ, также как инструкции JE эквивалентна инструкция JZ (это инструкции-синонимы).

Имеется еще одна инструкция цикла. Это инструкция JCXZ. Инструкция JCXZ осуществляет переход только в том случае, если значение регистра CX равно 0. Это дает удобный способ проверять регистр CX перед началом цикла. Например, в следующем фрагменте программы, при обращении к которому регистр BX указывает на блок байт, которые требуется обнулить, инструкция JCXZ используется для пропуска тела цикла в том случае, если регистр CX имеет зна-чение 0:

.

.

.

jcxz SkipLoop ; если CX имеет значение 0, то

; ничего делать не надо

ClearLoop:

mov BYTE PTR [si],0 ; установить следующий байт в

; значение 0

inc si ; ссылка на следующий очищаемый

; байт

SkipLoop:

.

.

.

Почему желательно пропустить выполнение цикла, если значение регистра CX равно 0? Потому что в противном случае значение CX будет уменьшено до величины 0FFFFh и инструкция LOOP осуществит переход на указанную метку. После этого цикл будет выполняться 65535 раз. Вы же хотели, чтобы значение регистра CX, равное 0, указывало, что требуется обнулить 0 байт, а не 65536. Инструкция JCXZ позволяет вам в этом случае быстро и эффективно выполнить нужную проверку.

Относительно инструкций циклов можно сделать пару интересных замечаний. Во-первых, нужно помнить о том, что инструкции циклов, как и инструкции условных переходов, могут выполнять переход только на метку, отстоящую от инструкции цикла не более чем на 128 байт в ту или другую сторону. Циклы, превышающие 128 байт, требуют использования условных переходов с помощью безусловных переходов (этот метод описан в предыдущем разделе «Условные пере-ходы»). Во-вторых, важно понимать, что ни одна из инструкций цик-лов не влияет на состояние флагов. Это означает, что инструкция:

loop LoopTop

не эквивалентна в точности инструкциям:

dec cx

jnz LoopTop

поскольку инструкция DEC изменяет флаги переполнения, знака, ну-ля, дополнительного переноса и четности, а инструкция LOOP на флаги не влияет. Кроме того, использование инструкции DEC не эк-вивалентно варианту:

sub cx,1

jnz LoopTop

поскольку инструкция SUB влияет на флаг переноса, а инструкция DEC — нет. Различия невелики, но при программировании на языке Ассемблера важно понимать, какие именно флаги устанавливают те или иные инструкции.

Загрузка...