Инструкция LOOP
Инструкция LOOP в Ассемблере уменьшает значение в регистре СХ в реальном режиме или ECX в защищённом. Если после этого значение в СХ не равно нулю, то команда LOOP выполняет переход на МЕТКУ. Синтаксис:
Состояние флагов не изменяется.
МЕТКА — это допустимый в Ассемблере идентификатор. О метках в Ассемблере я рассказывал здесь.
Алгоритм работы команды LOOP:
- CX = CX — 1
- Если CX не равен 0, то выполнить переход (продолжить цикл)
- Иначе не выполнять переход (прервать цикл и продолжить выполнение программы)
То есть команда LOOP выполняется в два этапа. Сначала из регистра СХ вычитается единица и его значение сравнивается с нулём. Если регистр не равен нулю, то выполняется переход к указанной МЕТКЕ. Иначе переход не выполняется и управление передаётся команде, которая следует сразу после команды LOOP.
Как выполнить цикл в Ассемблере
Выполнение цикла в Ассемблере можно организовать с помощью нескольких команд. Одна из таких команд — это команда LOOP. Команда цикла в Ассемблере всегда уменьшает значение счётчика на единицу. Это значение находится в регистре СХ (или ECX). Отличия между командами цикла заключаются только в условиях, при которых выполняется переход к метке или цикл завершается.
Команда LOOP выполняет переход к метке во всех случаях, когда значение в регистре СХ не равно нулю. Чтобы организовать цикл с помощью этой команды, нам надо сначала в регистр СХ записать число итераций цикла (то есть сколько раз цикл должен быть выполнен), затем вставить в код метку, а затем написать команды, которые должны быть выполнены в цикле. А уже в конце списка этих команд записать команду LOOP.
Более понятно это будет в примере программы (см. ниже).
Возможные ошибки
Начинающие довольно часто совершают одни и те же ошибки при организации циклов в Ассемблере. А именно — неправильно задают или обнуляют значение счётчика перед выполнение цикла.
При обнулении счётчика перед циклом при первой итерации цикла значение в регистре CX будет равно FFFFh (потому что команда LOOP отнимет от СХ единицу, а в СХ у нас был 0), и цикл в программе будет выполняться, соответственно 65536 раз.
Ещё один момент: диапазон адресов для передачи управления в команде LOOP ограничен в пределах -128…+127 байтов относительно адреса следующей команды. Если учесть, что в реальном режиме процессора средняя длина машинной команды равна 3 байта, то получается, что блок команд, выполняющихся в цикле, может состоять примерно из 42 команд. Если же в вашем цикле будет больше команд, то, например, MASM, выдаст сообщение об ошибке, которое будет выглядеть примерно так:
error A2075: jump destination too far : by 10 byte(s)
Здесь говорится, что местоположение перехода слишком далеко (примерно на 10 байт больше допустимого).
Ещё одна ошибка — это изменение значения регистра CX в теле цикла. В итоге команда LOOP будет работать неправильно. К тому же при этом можно попасть в бесконечный цикл. Пример:
Здесь в теле цикла увеличивается значение регистра СХ, поэтому он никогда не будет равен нулю, и, следовательно, цикл никогда не завершится.
А теперь о происхождении мнемоники LOOP. В общем то это не мнемоника, а слово. В переводе с английского оно означает “петля”, “виток”, “цикл”.
Ассемблер. Циклы
Инструкция JMP может быть использована для выполнения циклов. Например, следующий фрагмент кода может использоваться для выполнения тела цикла 10 раз:
Набор инструкций процессора включает в себя группу инструкций цикла для реализации итерации. Основная инструкция цикла LOOP имеет следующий синтаксис:
Где label — это метка, которая идентифицирует целевую инструкцию. Инструкция LOOP предполагает, что регистр ECX содержит в себе счетчик циклов. Когда инструкция цикла выполняется, регистр ECX уменьшается, точка выполнения программы переходит к метке до тех пор, пока значение регистра ECX (т.е. значение счетчика цикла) не достигнет нуля.
FasmWorld Программирование на ассемблере FASM для начинающих и не только
Учебный курс. Часть 13. Циклы и команда LOOP
Автор: xrnd | Рубрика: Учебный курс | 19-04-2010 | Распечатать запись
До этой части все наши программы выполнялись последовательно — в них не было ветвлений и переходов. Сегодня мы научимся делать простейшие циклы. Циклом называется повторяющееся выполнение последовательности команд. Но для начала нужно научиться объявлять метки.
Синтаксис объявления меток
Метка представляет собой символическое имя, вместо которого компилятор подставляет адрес. В программе на ассемблере можно присвоить имя любому адресу в коде или данных. Обычно метки используются для организации переходов, циклов или каких-то манипуляций с данными. По сути имена переменных, объявленных с помощью директив объявления данных, тоже являются метками. Но с ними компилятор дополнительно связывает размер переменной. Метка объявляется очень просто: достаточно в начале строки написать имя и поставить двоеточие. Например:
m1: mov ax,4C00h int 21h
Теперь вместо имени m1 компилятор везде будет подставлять адрес комады mov ax,4C00h. Можно объявлять метку на пустой строке перед командой:
exit_app: mov ax,4C00h int 21h
Имя метки может состоять из латинских букв, цифр и символов подчёркивания, но должно начинаться с буквы. Имя метки должно быть уникальным. В качестве имени метки нельзя использовать директивы и ключевые слова компилятора, названия команд и регистров (в этом случае FASM покажет сообщение об ошибке). FASM различает регистр символов в именах меток. Можно также объявлять несколько меток на один адрес. Например:
no_error: exit_app: m1: mov ax,4C00h
Подробнее о синтаксисе объявления меток рассказывается в части 27.
Команда LOOP
Для организации цикла предназначена команда LOOP. У этой команды один операнд — имя метки, на которую осуществляется переход. В качестве счётчика цикла используется регистр CX. Команда LOOP выполняет декремент CX, а затем проверяет его значение. Если содержимое CX не равно нулю, то осуществляется переход на метку, иначе управление переходит к следующей после LOOP команде.
Содержимое CX интерпретируется командой как число без знака. В CX нужно помещать число, равное требуемому количеству повторений цикла. Понятно, что максимально может быть 65535 повторений. Ещё одно ограничение связано с дальность перехода. Метка должна находиться в диапазоне -127…+128 байт от команды LOOP (если это не так, FASM сообщит об ошибке).
Пример цикла
В качестве примера я приведу простую программу, которая будет печатать все буквы английского алфавита. ASCII-коды этих символов расположены последовательно, поэтому можно выводить их в цикле. Для вывода символа на экран используется функция DOS 02h (выводимый байт должен находиться в регистре DL).
use16 ;Генерировать 16-битный код org 100h ;Программа начинается с адреса 100h mov ah,02h ;Для вызова функции DOS 02h — вывод символа mov dl,’A’ ;Первый выводимый символ mov cx,26 ;Счётчик повторений цикла metka: int 21h ;Обращение к функции DOS inc dl ;Следующий символ loop metka ;Команда цикла mov ah,09h ;Функция DOS 09h — вывод строки mov dx,press ;В DX адрес строки int 21h ;Обращение к функции DOS mov ah,08h ;Функция DOS 08h — ввод символа без эха int 21h ;Обращение к функции DOS mov ax,4C00h ;\ int 21h ;/ Завершение программы ;——————————————————- press: db 13,10,’Press any key. $’
Команды «int 21h» и «inc dl» (строки 8 и 9) будут выполняться в цикле 26 раз. Для того, чтобы программа не закрылась сразу, используется функция DOS 08h — ввод символа с клавиатуры без эха, то есть вводимый символ не отображается. Перед этим выводится предложение нажать любую кнопку (но Reset лучше не нажимать). Для примера адрес строки объявлен с помощью метки. Символы с кодами 13 и 10 обозначают переход на следующую строку (символ 13(0Dh) называется CR — Carriage Return — возврат каретки, а символ 10(0Ah) LF — Line Feed — перевод строки . Эти символы унаследованы со времён древних телетайпов, когда текст печатался, как на печатной машинке). Так выглядит результат работы программы:
Вложенные циклы
Иногда требуется организовать вложенный цикл, то есть цикл внутри другого цикла. В этом случае необходимо сохранить значение CX перед началом вложенного цикла и восстановить после его завершения (перед командой LOOP внешнего цикла). Сохранить значение можно в другой регистр, во временную переменную или в стек. Следующая программа выводит все доступные ASCII-символы в виде таблицы 16×16. Значение счётчика внешнего цикла сохраняется в регистре BX.
use16 ;Генерировать 16-битный код org 100h ;Программа начинается с адреса 100h mov ah,02h ;Для вызова функции DOS 02h — вывод символа sub dl,dl ;Первый выводимый символ mov cx,16 ;Счётчик внешнего цикла (по строкам) lp1: mov bx,cx ;Сохраняем счётчик в BX mov cx,16 ;Счётчик внутреннего цикла (по столбцам) lp2: int 21h ;Обращение к функции DOS inc dl ;Следующий символ loop lp2 ;Команда внутреннего цикла mov dh,dl ;Сохраняем значение DL в DH mov dl,13 ;\ int 21h ; \ mov dl,10 ; / Переход на следующую строку int 21h ;/ mov dl,dh ;Восстанавливаем значение DL mov cx,bx ;Восстанавливаем значение счётчика loop lp1 ;Команда внешнего цикла mov ah,09h ;Функция DOS 09h — вывод строки mov dx,press ;В DX адрес строки int 21h ;Обращение к функции DOS mov ah,08h ;Функция DOS 08h — ввод символа без эха int 21h ;Обращение к функции DOS mov ax,4C00h ;\ int 21h ;/ Завершение программы ;——————————————————- press db 13,10,’Press any key. $’
Как видите, всё довольно просто Результат работы программы выглядит вот так:

Упражнение
Напишите программу для вычисления степени числа 3 по формуле a = 3 n . Число a — 16-битное целое без знака, число n — 8-битное целое без знака (используйте n<11, чтобы избежать переполнения). Проверьте работу программы в отладчике (нажимайте F7 на команде LOOP, чтобы осуществить переход). Результаты можете выкладывать в комментариях.
Урок 5
На прошлом уроке для того, чтобы сделать задержку и считать символ с клавиатуры, мы были вынуждены использовать цикл. Сегодня мы более подробно познакомимся с непоследовательным выполнением команд. Большинство программ содержат ряд циклов, в которыхнесколько команд повторяются до достижения определенного требования, и различные проверки, определяюшие, какие из нескольких действий следует выполнять. Обычным требованием является проверка — должна ли программа завершить выполнение. Эти требования включают передачу управления по адресу команды, которая не находится непосредственно за выполняемой в текущий момент командой. Передача управления может осуществляться вперед для выполнения новой группы команд или назад для повторения уже выполненных команд. Некоторые команды могут передавать управление, изменяя нормальную последовательность шагов непосредственной модификацией значения смещения в командном указателе. Ниже приведены четыре способа передачи управления (все будут рассмотрены в этой главе):
Заметим, что имеется три типа адресов: SHORT, NEAR и FAR. Адресация SHORT используется при циклах, условных пеpеходах и некоторых безусловных переходах. Адресация NEAR и FAR используется для вызовов процедур (CALL) и безусловных переходов, которые не квалифицируются , как SHORT. Все три типа передачи управления воздействуют на содержимое регистра IP; тип FAR также изменяет регистр CS.
Команда JMP
Одной из команд, обычно используемых для передачи управле ния является команда JMP. Эта команда выполняет безусловный переход, т.е. обеспечивает передачу управления при любых обстоятельствах. Команда JMP для перехода в пределах -128 до +127 байт имеет тип SHORT. Ассемблер генерирует в этом случае однобайтовый операнд в пределах от 00 до FF. Команда JMP, превосходящая эти пределы, получает тип FAR, для которого генерируется другой машинный код и двухбайтовый операнд. Ассемблер в первом просмотре исходной программы определяет длину каждой команды. Однако, команда JMP может быть длиной два или три байта. Если к моменту просмотра команды JMP ассемблер уже вычислил значение опеpанда (при переходе назад):
то он генерирует двухбайтовую команду. Если ассемблер еще не вычислил значение операнда (при переходе вперед)
то он не знает тип перехода NEAR или FAR, и автоматически генерирует 3-х байтовую команду. Для того, чтобы указать ассемблеру на необходимость генерации двухбайтовой команды, следует использовать оператор SHORT:
В качестве полезного упражнения, введите программу, проассемблируйте ее, скомпануйте и переведите в COM-формат. Определение данных не требуется, поскольку непосредственные операнды генерируют все необходимые данные. Используйте отладчик DEBUG для пошагового выполнения COM-модуля и просмотрите несколько повторений цикла. Когда регистр AX будет содержать 08, BX и CX увеличатся до шест. 24 (дес. 36) и шест. 80 (дес. 128), соответственно. Для выхода из отладчика используйте команду Q.
команда LOOP
Команда JMP в примере на рис. 7.1 реализует бесконечный цикл. Но более вероятно подпрограмма должна выполнять определенное число циклов. Команда LOOP, которая служит для этой цели, использует начальное значение в регистре CX. В каждом цикле команда LOOP автоматически уменьшает содержимое регистра CX на 1. Пока значение в CX не равно нулю, управление передается по адресу, указанному в операнде, и если в CX будет 0, управление переходит на слудующую после LOOP команду. Аналогично команде JMP, операнд команды LOOP определяет расстояние от конца команды LOOP до адреса метки A20, кото рое прибавляется к содержимому командного указателя. Для команды LOOP это расстояние должно быть в пределах от -128 до +127 байт. Если операнд превышает эти границы, то ассемб лер выдаст сообщение «Relative jump out of range» (превышены границы перехода). Дополнительно существует две разновидности команды LOOP — это LOOPE (или LOOPZ) и LOOPNE (или LOOPNZ). Обе команды также уменьшают значение регистра CX на 1. Команда LOOPE передает управление по адресу операнда, если регистр CX имеет ненулевое значение и флаг нуля установлен (ZF=1). Команда LOOPNE передает управление по адресу операнда, если регистр CX имеет ненулевое значение и флаг нуля сброшен (ZF=0).
Регистр флагов (Flags)
Следующий материал данной главы требует более детального ознакомления с флаговым регистром. Этот pегистр содержит 16 бит флагов, которые управляются различными командами для индикации состояния операции. Во всех случаях флаги сохраня ют свое значение до тех пор, пока другая команда не изменит его. Флаговый регистр содержит следующие девять используемых бит (звездочками отмечены неиспользуемые биты):
Рассмотрим эти флаги в последовательности справа налево.
CF (Carry Flag)
флаг переноса. Содержит значение «переносов» (0 или 1) из старшего разряда при арифметичес ких операциях и некоторых операциях сдвига и циклического сдвига (см. гл.12).
PF (Parity Flag)
флаг четности. Проверяет младшие восемь бит pезультатов операций над данными. Нечетное число бит приводит к установке этого флага в 0, а четное — в 1. Не следует путать флаг четности с битом контроля на четность.
AF (Auxiliary Carry Flag)
дополнительный флаг переноса. Устанавливается в 1, если арифметическая операция приводит к переносу четвертого бита справа (бит номер 3) в регистро вой однобайтовой команде. Данный флаг имеет отношение к арифметическим операциям над символами кода ASCII и к десятичным упакованным полям.
ZF (Zero Flag)
флаг нуля. Устанавливается в качестве результата aрифметических команд и команд сравнения. Как это ни странно, ненулевой результат приводит к установке нулевого значения этого флага, а нулевой — к установке единичного значения. Кажущееся несоответствие является, однако, логически правильным, так как 0 обозначает «нет» (т.е. результат не равен нулю), а единица обозначаeт «да» (т.е. результат равен нулю). Команды условного перехода JE и JZ проверяют этот флаг.
SF (Sign Flag)
знаковый флаг. Устанавливается в соответ ствии со знаком результата (старшего бита) после арифмети ческих опеpаций: положительный результат устанавливает 0, а отрицательный — 1. Команды условного перехода JG и JL проверяют этот флаг.
TF (Trap Flag)
флаг пошагового выполнения. Этот флаг вам уже приходилось устанавливать, когда использовалась ко манда Т в отладчике DEBUG. Если этот флаг установлен в еди ничное cостояние, то процессор переходит в режим пошагового выполнения команд, т.е. в каждый момент выполняется одна команда под пользовательским управлением.
IF (Interrupt Flag)
флаг прерывания. При нулевом состоя нии этого флага прерывания запрещены, при единичном — разрешены.
DF (DIrection Flag)
флаг направления. Используется в строковых операциях для определения направления передачи данных. При нулевом состоянии команда увеличивает содержимое регистров SI и DI, вызывая передачу данных слева направо, при нулевом — уменьшает содержимое этих регистров, вызывая передачу данных справа налево.
OF (Overflow Flag)
флаг переполнения. Фиксирует арифме тическое переполнение, т.е. перенос в/из старшего (знаково го) бита при знаковых арифметических операциях. В качестве примера: команда CMP сравнивает два операнда и воздействуте на флаги AF, CF, OF, PF, SF, ZF. Однако, нет необходимости проверять все эти флаги по отдельности. В сле- дующем примере проверяется содержит ли регистр BX нулевое значение:
Если BX содержит нулевое значение, команда CMP устанавливает флаг нуля ZF в единичное состояние, и возможно изменяет (или нет) другие флаги. Команда JZ (перехлд если нуль) проверяет только флаг ZF. При единичном значении ZF, обозначающее нулевой признак, команда передает управление на адрес, указанный в ее операнде, т.е. на метку B50. В предыдущих примерах было показано, что команда LOOP уменьшает на единицу содержимое регистра CX и проверяет его: если не ноль, то управление передается по адресу, указанному в операнде. Таким образом, передача управления зависит от конкретного состояния. Ассемблер поддерживает большое количество команд условного перехода, которые осуществляют передачу управления в зависимости от состояний флагового регистра. Например, при сравнении содержимого двух полей последующий переход зависит от значения флага. Команду LOOP в программе на рис.7.2 можно заменить на две команды: одна уменьшает содержимое регистра CX, а другая выполняет условный переход:
Команды DEC и JNZ действуют аналогично команде LOOP: уменьшают содержимое регистра CX на 1 и выполняет переход на метку A20, если в CX не ноль. Команда DEC кроме того устанавливает флаг нуля во флаговом регистре в состояние 0 или 1. Команда JNZ затем проверяет эту установку. В рассмот ренном примере команда LOOP хотя и имеет огпаниченное исполь зование, но более эффективна, чем две команды: DEC и JNZ. Аналогично командам JMP и LOOP операнд в команде JNZ cодержит значение расстояния между концом команды JNZ и адресом A20, которое прибавляется к командному указателю. Это расстояние должно быть в пределах от -128 до +127 байт. В случае перехода за эти границы ассемблер выдаст сообщение «Relative jump out of range» (превышены относительные грани цы перехода).
Знаковые и беззнаковые данные
Рассматривая назначение команд условного перехода следует пояснить характер их использования. Типы данных, над которы ми выполняются арифметические операции и операции сравнения определяют какими командами пользоваться: беззнаковыми или знаковыми. Беззнаковые данные используют все биты как биты данных; характерным примером являются символьные строки: имена, адреса и натуральные числа. В знаковых данных самый левый бит представляет собой знак, причем если его значение равно нулю, то число положительное, и если единице, то отрицательное. Многие числовые значения могут быть как положительными так и отрицательными. В качестве примера предположим, что регистр AX содержит 11000110, а BX — 00010110. Команда
сравнивает содержимое регистров AX и BX. Если данные беззнаковые, то значение в AX больше, а если знаковые — то меньше.
Условные переходы
Переходы для беззнаковых данных
Любую проверку можно кодировать одним из двух мнемоничес ких кодов. Например, JB и JNAE генерирует один и тот же объектный код, хотя положительную проверку JB легче понять, чем отрицательную JNAE.
Переходы для знаковых данных
Команды перехода для условия равно или ноль (JE/JZ) и не равно или не ноль (JNE/JNZ) присутствуют в обоих списках для беззнаковых и знаковых данных. Состояние равно/нуль происходит вне зависимости от наличия знака.
Специальные арифметические проверки
Еще одна команда условного перехода проверяет равно ли содержимое регистра CX нулю. Эта команда необязательно должна pасполагаться непосредственно за командой арифметики или сравнения. Одним из мест для команды JCXZ может быть начало цикла, где она проверяет содержит ли регистр CX ненулевое значение. Не спешите пока заучивать эти команды наизусть. Запомните только, что для беззнаковых данных есть переходы по состоя ниям равно, выше или ниже, а для беззнаковых — равно, больше или меньше. Переходы по проверкам флагов переноса, переполнения и паритета имеют особое назначение. Ассемблер транслирует мнемонические коды в объектный код независимо от того, какую из двух команд вы применили. Однако, команды JAE и JGE являясь явно одинаковыми, проверяют различные флаги.