CodeNet / Языки программирования / Ассемблер / СПРАВОЧНИК по системе программирования ТУРБО АССЕМБЛЕР 2.0
СПРАВОЧНИК по системе программирования ТУРБО АССЕМБЛЕР 2.0
Условные переходы ----------------------------------------------------------------- Описанные в предыдущем разделе инструкции переходов - это только часть того, что вам потребуется для написания полезных программ. В действительности необходима возможность писать такие программы, которые могут принимать решения. Именно это можно де- лать с помощью операций условных переходов. Инструкция условного перехода может осуществлять или нет переход на целевую (указанную в ней) метку, в зависимости от сос- тояния регистра флагов. Рассмотрим следующий пример: . . . mov ah,1 ; функция DOS ввода с клавиату- ; ры int 21h ; получить следующую нажатую ; клавишу cmp al,'A' ; была нажата буква "A"? je AWasTyped ; да, обработать ее mov [TampByte], al ; нет, сохранить символ . . . AWasTyped: push ax ; сохранить символ в стеке . . . Сначала в данной программе с помощью функции операционной системы DOS воспринимается нажатая клавиша. Затем для сравнения введенного символа с символом A используется инструкция CMP. Эта инструкция аналогична инструкции SUB, только ее выполнение ни на что не влияет, поскольку назначение данной инструкции состоит в том, чтобы можно было сравнить два операнда, установив флаги так же, как это делается в инструкции SUB. Поэтому в предыдущем при- мере флаг нуля устанавливается в значение 1 только в том случае, если регистр AL содержит символ A. Теперь мы подошли к основному моменту. Инструкция JE предс- тавляет инструкцию условного перехода, которая. осуществляет пе- редачу управления только в том случае, если флаг нуля равен 1. В противном случае выполняется инструкция, непосредственно следую- щая за инструкцией JE (в данном случае - инструкция MOV). Флаг нуля в данном примере будет установлен только в случае нажатия клавиши A, и только в этом случае процессор 8086 перейдет к вы- полнению инструкции с меткой AWasTyped, то есть инструкции PUSH. Набор инструкций процессора 8086 предусматривает большое разнообразие инструкций условных переходов, что позволяет вам осуществлять переход почти по любому флагу или их комбинации. Можно осуществлять условный переход по состоянию нуля, переноса, по знаку, четности или флагу переполнения и по комбинации фла- гов, показывающих результаты операций чисел со знаками. Перечень инструкций условных переходов приводится в Таблице 5.2. Инструкции условных переходов Таблица 5.2 ----------------------------------------------------------------- Название Значение Проверяемые флаги ----------------------------------------------------------------- JB/JNAE Перейти, если меньше / перейти, если CF = 1 не больше или равно JAE/JNB Перейти, если больше или равно / пе- CF = 0 рейти, если не меньше JBE/JNA Перейти, если меньше или равно / пе- CF = 1 или ZF = 1 рейти, если не больше JA/JNBE Перейти, если больше / перейти, если CF = 0 и ZF = 0 не меньше или равно JE/JZ Перейти, если равно ZF = 1 JNE/JNZ Перейти, если не равно ZF = 0 JL/JNGE Перейти, если меньше чем / перейти, SF = OF если не больше чем или равно JGE/JNL Перейти, если больше чем или равно / SF = OF перейти, если не меньше чем JLE/JNLE Перейти, если меньше чем или равно / ZF = 1 или SF = OF перейти, если не больше, чем JG/JNLE Перейти, если больше чем / перейти, ZF = 0 или SF = OF если не меньше чем или равно JP/JPE Перейти по четности PF = 1 JNP/JPO Перейти по нечетности PF = 0 JS Перейти по знаку SF = 1 JNS Перейти, если знак не установлен SF = 0 JC Перейти при наличии переноса CF = 1 JNC Перейти при отсутствии переноса CF = 0 JO Перейти по переполнению OF = 1 JNO Перейти при отсутствии переполнения OF = 0 ----------------------------------------------------------------- CF - флаг переноса, SF - флаг знака, OF - флаг переполне- ния, ZF - флаг нуля, PF - флаг четности Более подробная информация об инструкциях-синонимах и общие сведения об инструкциях перехода содержатся в Главе 6. Там также подробно рассказывается о способах, с помощью которых инструкции процессора 8086 могут изменять регистр флагов. Несмотря на свою гибкость, инструкции условного перехода имеют также серьезные ограничения, поскольку переходы в них всег- да короткие. Другими словами целевая метка, указанная в инструк- ции условного перехода, должна отстоять от инструкции перехода не более, чем на 128 байт. Например, Турбо Ассемблер не может ас- семблировать: . . . JumpTarget: . . . DB 1000 DUP (?) . . . dec ax jnz JumpTarget . . . так как метка JumpTarget отстоит от инструкции JNZ более чем на 1000 байт. В данном случае нужно сделать следующее: . . . JumpTarget: . . . DB 1000 DUP (?) . . . dec ax jnz SkipJump jmp JumpTarget SkipJump: . . . где условный переход переход применяется для того, чтобы опреде- лить, нужно ли выполнить длинный безусловные переход. Циклы ----------------------------------------------------------------- Одним из видов конструкций в программе, которые можно пост- роить с помощью условных переходов, являются циклы. Цикл - это просто-напросто блок кода, завершающийся условным переходом, бла- годаря чему данных блок может выполняться повторно до достижения условия завершения. Возможно, вам уже знакомы такие конструкции циклов, как 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 - нет. Различия невелики, но при программировании на языке Ассемблера важно понимать, какие именно флаги устанавливают те или иные инструкции.