Электронный учебник - Assembler - Ответ 14354755

ГЛАВА 13
ПЕРЕХОДЫ И ЦИКЛЫ
(часть 4/5)
Команды управления циклом

С помощью условных операторов и логических и арифметических команд теоретически можно организовать сколь угодно сложный алгоритм. Однако на практике при необходимости можно организовать обработку тысяч элементов массива (например, присвоить каждому элементу начальное значение), вручную набирать тысячу операторов присваивания крайне тяжело.

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

Рис. 13.3.1

Рисунок |Паскаль| BASIC |Си, Си++

if X>0 then S1

else S2;|IF X>0 THEN S1

ELSE S2

if (X>0) S1

else S2;

(b) |while X>0 do S;| DO WHILE X>0 S

LOOP

|while (X>0) S;

(c) |repeat S until X<0|| do S until (X<0);

Где S, S1 и S2 какие-то операторы, а X – знаковая переменная, скрываются следующие ассемблерные команды

| | | | | | |
| --- | --- | --- | --- | --- | --- |
| | CMP X,0 | BEG: | CMP X,0 | BEG: | S |
| | JLE L2 | | JLE FIN | | CMP X,0 |
| | S1 | | S | | JG BEG |
| | JMP FIN | | JMP BEG | | |
| L2: | S2 | FIN: | | | |
| FIN: | | | | | |
| | (a) | | (b) | | (c) |

Если Вам необходимо по каким-то причинам скрыть условный переход, то помните, что конструкция b*x + NOT(b)*y эквивалентна условному выражению if (b==TRUE) then x else y. Вместо умножения и сложения можно использовать логическое умножение (AND) и сложение (OR).

Циклы

Допустим в вашей программе необходимо решить уравнение (A+B)*C=D. Хорошо, если известно, что A=1, B=5, C=7. Посчитать D – нет никаких проблем. А если эти числа получаются после других восьмиэтажных уравнений? Подойдем к решению задачи творчески (для простоты считаем что размерность A, B и C – байт):

AssemblerВыделить код

1
2
3
4

mov al,A
add al,B ;al=A+B
mul C ;ax=(A+B)C
mov D,ax ;D=(A+B)C

Допустим, надо пересчитать этот фрагмент 10 раз с разными значениями A, B и C. Как? Десять раз переписать? Нет – нам нужно каким-то образом заставить компьютер выполнять повторяющиеся вычисления. Вообще говоря, именно эта потребность является одной из главных причин использования машин для выполнения арифметических вычислений. Цикл – это набор операций, который регулярно повторяется в одной и той же последовательности.

Существует три основных типа цикла:

циклы с условием в начале

,

циклы с условием в конце

и

циклы с условием в середине

. Кроме того, существуют

комбинированные циклы

, которые имеют несколько условий в разных местах, например в начале и конце одновременно. Циклы с условием в начале – это циклы с предусловием, while-циклы. Цикл с условием в конце – это цикл с пост-условием, do /while или repeat / until – циклы. Блок статического предсказания направлений ветвлений процессоров Pentium оптимизирован под переходы, направленные в область младших адресов (назад). Поэтому циклы с постусловием должны выполняться быстрее циклов с предусловием. Циклы со счетчиком представляют собой синтаксическую разновидность циклов с предусловием. Замена цикла с приращением на цикл с убыванием счетчика делает ненужной команду CMP. Цикл с условием в середине называется break-циклы.

Существует целых два основных варианта оператора повторения, причем каждый для своих целей:

1) повторение, управляемое вычисляемым условием, и

2) повторение, управляемое явным счетчиком или списком значений.

В первую группу входят два оператора:

while b do S
и
repeat S until not b

Среди циклов на практике часто встречаются циклы с заранее известным числом повторений (for-циклы), поэтому в систему команд многих ЭВМ обычно включают дополнительные команды, упрощающие реализацию подобных циклов.

Команда LOOP (LOOPW, LOOPD)

Синтаксис команды

:

LOOP <TARGET>

Пусть некоторую группу команд (тело цикла) надо повторить N раз (N>0). Когда-то на языке ассемблера этот цикл реализовали по такой схеме:

AssemblerВыделить код

1
2
3
4
5

MOV CX,N ;CX – счетчик цикла
LOOP: . . . ;тело цикла
DEC CX ;CX-1->CX
CMP CX,0 ;CX=0?
JNE LOOP ;если CX не равен 0 идти к метке LOOP

Создатели языка ассемблера обратили внимание, что в конце таких циклов всегда применяется одна и та же тройка команд (dec cx/cmp cx,0/jne loop). Учитывая это, в систему команд была введена специальная команда LOOP («петля»), которая объединяет в себе действие всех этих команд.

Действие команды LOOP немного сходно с циклом FOR-NEXT в Бейсике или соответствует паре инструкций REAPET-UNTIL, где инструкция REAPET означает начало блока инструкций цикла. Как и для цикла FORNEXT в Бэйсике, мы должны сообщить LOOP о том, сколько раз запускать цикл. Мы делаем это, помещая счетчик повторений в регистр RCX/ECX/CX. В каждом цикле микропроцессор вычитает единицу из RCX/ECX/CX, и когда RCX/ECX/CX становится равным нулю, LOOP заканчивает цикл. С помощью команды LOOP наш цикл запишется следующим образом:

AssemblerВыделить код

1
2
3
4
5
6
7
8
9

 mov cx,N;cx – счетчик цикла
;-- тело цикла-------------
L: mov al,A
add al,B;al=A+B
mul C;ax=(A+B)C
mov D,ax;D=(A+B)C
. . .
;----------------------------
LOOP L

Получилось короче, и работает команда LOOP быстрее трех команд.

Необходимо учитывать ряд особенностей команды LOOP:

команда LOOP требует, чтобы в качестве счетчика цикла обязательно использовался регистр CX/ECX;
начальное значение регистру CX/ECX должно быть присвоено до начала цикла;
так как команда LOOP ставится в конце цикла, тело цикла хотя бы раз должно выполниться;
если по какой-либо причине перед началом цикла регистр CX/ECX равен 0, то тело цикла все равно будет выполнено, из содержимого регистра CX/ECX будет отнята 1 и уже после этого регистр CX/ECX будет проверен на равенство 0. Что, по вашему, окажется в регистре CX/ECX? Правильно, содержимое регистра CX/ECX равно –1 (0FFFFh/0FFFFFFFFh) и цикл, вместо того чтобы остановиться, будет запущен еще 65535 раз, если мы используем регистр CX, или 4 миллиона раз (!), если использовали регистр ECX, – это один из источников ошибок.
Поэтому, если возможен вариант, что число повторений может быть и нулевым, то при CX /ECX =0 надо делать обход цикла:

AssemblerВыделить код

1
2
3
4
5


MOV ECX,N ;N>=0
JECXZ L1 ;если ECX=0 перейти на L1
L: … ;
LOOP L
L1: . . .

Именно ради осуществления таких обходов и были введены команды условного перехода JCXZ/JECXZ;
команда LOOP реализует только короткий переход, поэтому расстояние от LOOP до начала цикла не должно превышать 127 байтов вниз или 128 байтов вверх.

Процедуры возвращают какое-либо значение в регистре EAX, которые на равенство/неравенство нулю проверяют последовательностью команд:

Самый неоптимальный вариант
cmp eax,0
83F800 или 3D00000000
3 байта или 5 байтов
jz TARGET
74XX или 0F84XXXXXXXX
2 короткий или 6 длинный
Итого: от 5 до 11 байт

Более короткий вариант – TEST ничего не пишет в регистр (OR и AND пишут),

поэтому эта инструкция лучше спаривается, значит и код будет более быстрым
test eax,eax/
85C0 или
2 байта
and eax,eax/
23C0 или
or eax,eax
0BC0
jz TARGET
74XX или 0F84XXXXXXXX
2 или 6 байтов
Итого: от 4 до 8 байт

Самый короткий вариант – Если вам не важно содержимое регистра ECX

или неважно, где будет находится содержимое EAX
xchg eax,ecx
91
1 байт
jecxz TARGET
E3XX
2 байта
Итого: 3 байта

Иногда по смыслу требуется команда jecxnz TARGET, которую можно сэмулировать последовательностью inc ecx/loop TARGET команда inc ecx необходима потому, что loop вычтет 1 из содержимого ecx и если там окажется 0, перехода к TARGET не произойдет.

Если вам необходим бесконечный цикл – воспользуйтесь командой JMP, а если не знаете сколько раз придется запускать цикл, используйте команды условного перехода.

Если программа транслируется в 32-разрядном режиме, команда LOOP автоматически использует регистр ECX в качестве счетчика циклов.

Команда LOOPW использует регистр CX в качестве счетчика циклов в 32-разрядном режиме. Фактически это та же самая LOOP, предваряемая префиксом 67h. Если программа транслируется в 16-разрядном режиме, команда LOOPD использует регистр ECX в качестве счетчика циклов. Фактически это та же LOOP, предваряемая префиксом 67h.

Команды LOOPE/LOOPZ и LOOPNE/LOOPNZ
(Цикл по счетчику пока равно/ = “LOOP if Equal”
Цикл по счетчику пока ноль = “LOOP if Zero”
Цикл по счетчику пока не равно = “LOOP if Not Equal”
Цикл по счетчику пока не ноль = “LOOP if Not Zero”)

Синтаксис команд

:

LOOPE <TARGET>
LOOPZ <TARGET>
LOOPNE <TARGET>
LOONZ <TARGET>

Эти команды похожи на команду LOOP, то есть заставляют цикл повториться столько раз, сколько указано в регистре CX/ECX, однако они допускают и досрочный выход из цикла.

Команда LOOPE используется для организации цикла с известным числом повторений, из которого возможен досрочный выход. До начала цикла в регистр CX/ECX записывается число повторений. Команда LOOPE ставится в конец цикла, а перед ней помещается команда, меняющая флаг ZF – обычно это команда сравнения CMP. Команда LOOPE заставляет цикл повторяться CX/ECX раз, но только если команда CMP фиксирует неравенство сравниваемых величин – происходит выход из цикла. По какой именно причине произошел выход из цикла (по ZF=0 или CX/ECX=0), надо проверять после цикла. Чаще всего команда LOOPE используется для поиска первого элемента некоторой последовательности, отличного от заданной величины. Команда LOOPZ – синоним команды LOOPE.

Команда LOOPNE аналогична команде LOOPE, но выход из цикла осуществляется при ZF=1, если предыдущая команде LOOPNE команда CMP зафиксировала равенство или по CX/ECX=0. Команда LOOPNE обычно используется для поиска в некоторой последовательности первого элемента, имеющего заданную величину. Команда LOOPNZ – синоним команды LOOPNE.

Пусть ASCII_STR является переменной, ассоциируемой с началом цепочки из L символов, и в цепочке необходимо найти символ пробела. Цикл заканчивается, когда обнаружен пробел или когда просмотрена вся цепочка. Если пробел не найден, осуществляется переход к метке NOT_FOUND:

AssemblerВыделить код

1
2
3
4
5
6

 MOV ECX,L ;передать размер массива в ECX
MOV EDI,offset ASCII_STR ;инициализировать индекс
MOV AL,’ ’ ;загрузить в AL код пробела
NEXT: SCASB ;проверить на символ пробела
LOOPNE NEXT ; зациклить, если не пробел и ECX не равен 0
JNZ NOT_FOUND ; перейти к NOT_FOUND,если пробел не найден

Вернуться к обсуждению:

Электронный учебник Assembler