4.9.6. Управление циклами

Существует несколько команд условного перехода, предназначенных для управления циклами в программах. Поскольку программые циклы используются часто, желательно эффективное управление циклом. На Фиг. 4.30 показаны четыре команды, созданные для того, чтобы облегчить программирование циклов на языке ассемблера микропроцессора 8088.

Так же, как строковые команды используют регистр CX в качестве счетчика, команды цикла LOOP используют регистр CX в качестве счетчика цикла. Все эти команды неявно рассматривают регистр CX как счетчик итераций цикла. Простейшая команда среди них - команда LOOP. Команда LOOP уменьшает регистр CX и передает управление на метку, если содержимое регистра CX не равно 0. Если вычитание единицы из регистра CX не привело к нулевому результату, команда LOOP не делает перехода, и выполняется следующая команда.

Приведенный ниже программный фрагмент демонстрирует обычное использование команды LOOP.


          MOV     CX,LOOP_COUNT
BEGIN_LOOP:
          ; ...   тело цикла
          LOOP    BEGIN_LOOP


Microsoft (R) Macro Assembler Version 5.00                1/1/80 04:00:49
Фиг. 4.30 Команды цикла                                         Page  1-1
                         PAGE     ,132
                         TITLE    Фиг. 4.30 Команды цикла
0000                     CODE     SEGMENT
                         ASSUME   CS:CODE
                         ;----------------------------------------
                         ; В этом примере демонстрируются команды цикла.
                         ; Команды в примере не являются законченной программой.
                         ;----------------------------------------
0000    E3 06                     JCXZ      END_OF_LOOP  ; Конец цикла, если CX равно 0
0002                     BEGIN_LOOP:
                         ; ....   Тело цикла
0002    E2 FE                     LOOP      BEGIN_LOOP   ; Переход пока регистр CX 
                                                         ; не станет равен 0
                         ; ....   Если проверяется какое-либо условие, то
0004    E1 FC                     LOOPE     BEGIN_LOOP   ; Переход по равенству в условии и
                                                         ; значение регистра CX не равно 0
                         ; ....   Или
0006    E0 FA                     LOOPNE    BEGIN_LOOP   ; Переход по неравенству в условиии и
                                                         ; начение регистра CX не равно 0
0008                     END_OF_LOOP:
0008                     CODE     ENDS
                         END

                                  Фиг. 4.30 Команда цикла

Программа помещает число итераций цикла в регистр CX перед выполнением цикла. Затем выполняется тело цикла, а следом за ним команда LOOP. Она уменьшает счетчик на единицу, что соответствует единственной, только что выполненной итерации цикла. Если теперь счетчик в регистре CX равен 0, программа продолжает выполняться после команды LOOP. Если счетчик не равен 0, управление возвращается к началу цикла, чтобы совершить еще один проход по телу цикла. Тело цикла выполняется столько раз, сколько было сначала задано содержимым регистра CX. Единственное важное замечание: если программа внутри цикла изменяет регистр CX, число итераций цикла не будет соответствовать начальному значению в регистре CX.

Описанный метод одинаково хорошо работает, когда число циклов известно во время ассемблирования (как в примере, где LOOP_COUNT - непосредственно заносимое значение), и когда число циклов определяется во время выполнения. Если вычисленное число оказалось равным 0, цикл выполнится 65536 раз. Когда микропроцессор 8088 выполняет первую команду LOOP, он уменьшает CX от 0 до 0FFFFH, и поскольку теперь регистр CX ненулевой, повторяет цикл. Таким образом, загрузка нулевого значения счетчика циклов - специальный случай. Этот специальный случай обрабатывается командой JCXZ (переход, если содержимое регистра CX равно 0). Эта команда проверяет текущее содержимое регистра CX, и делает переход, если оно равно нулю. Команда не проверяет ни одного флага, и не влияет ни на один из них. Следующий пример аналогичен предыдущему, за исключением того, что он загружает регистр CX из ячейки памяти, содержимое которой вычисляется во время выполнения программы. По этой причине может оказаться, что счетчик циклов нулевой, и пример использует команду JCXZ, чтобы проверить, нужно ли полностью пропустить тело цикла.


        MOV       CX,LOOP_COUNT_WORD
        JCXZ      END_OF_LOOP
BEGIN_LOOP:
        ; ... тело цикла
        LOOP      BEGIN_LOOP
END_OF_LOOP:

В программе не нужно использовать команду JCXZ в каждом цикле с вычисляемым счетчиком. Если программист знает, что счетчик циклов никогда не будет равен нулю, проверка не нужна. Однако опыт показывает, что значение, которое "никогда" не должно появиться, обычно появляется в первую очередь, как только вы начинаете выполнять программу.

Оставшиеся две команды цикла предоставляют еще большие возможностей при управлении циклами. Эти команды аналогичны префиксам REPE и REPNE. Если команда LOOP выходит из цикла, только когда в регистре CX оказывается нуль, то команда LOOPE (цикл, пока равно) выходит из цикла, если установлен флаг нуля, или если в регистре CX получился 0. Тем самым становится возможным двойственное завершение цикла. Программа может загрузить в регистр CX максимальное число итераций цикла, а затем проверять флаг нуля в конце каждого цикла на условие завершения. Команда LOOPNE (цикл, пока не равно) выполняет обратную к описанной проверку флага нуля: цикл здесь завершается, если регистр достиг нуля, или если установлен флаг нуля. Следующий пример показывает использование команды LOOPNE. В примере складываются два списка чисел, чтобы найти пару элементов, сумма которых точно равна 100. Так как в каждой итерации перед проверкой складываются два чила, команду REPNE CMPSB использовать нельзя.

В примере предполагается, что пары регистров DS:SI и ES:DI инициализированы так, чтобы указывать на эти списки.


        MOV     CX,MAX_LOOP_COUNT   ;максимальное число заходов
BEGIN_LOOP:
        LODSB                       ;чтение числа из первого списка
        ADD     AL,ES:[DI]          ;прибавить из второго списка
        INC     DI                  ;указатель на следующий элемент
        CMP     AL,100              ;проверка на нужное значение
        LOOPNE  BEGIN_LOOP          ;снова, если не равно и не все
        JE      MATCH_FOUND         ;переход сюда, чтобы определить конец