Циклы

Последнее обновление: 02.07.2023

Циклические конструкции позволяют выполнить некоторый набор инструкций определенное количество раз. На ассемблере можно реализовать различные типы циклов различным образом. Но, как правило, они вовлекают инструкцию сравнения результатов CMP и переход к определенной метке.

while

В ряде языков программирования есть тип циклов - цикл while, который выполняет некоторые действия, пока истинно некоторое условие:

// бейсикоподобный синтаксис
WHILE условие
    действия цикла
END WHILE

// сиподобный синтаксис
while(условие){
    действия цикла
}

Пример цикла на сиподобных языках:

i = 0;
while(i < 100)
{
    i++;
}

Здесь в цикле увеличиваем переменную i на 1, пока она меньше 100. Когда переменная i окажется равна 100, выходим из цикла.

Реализация подобного цикла на ассемблере:

.data
    i dword 0
.code
main proc
    mov i, 0
while_start:
    cmp i, 10
    jnl while_end   ; если i не меньше 10, переход к метке while_end
    ; выполняемые действия
    inc i
    jmp while_start
while_end:
    mov eax, i      ; EAX = 10
    ret
main endp
end

Здесь метка while_start знаменует начало цикла, в котором сначала сравниваем значение i с числом 10. Если значение i НЕ меньше 10 (jnl), переходим к концу цикла - метке while_end. Иначе инкрементируем значение переменной и переходим к началу цикла - метке while_start.

цикл do..while

Еще одним типом циклов является цикл do..while - он сначала выполняет некоторые действия, а потом проверяет условие. Если условие верно, повторяет действия. Если условие НЕ верно, завершает работу:

// сиподобный синтаксис
do{
    выполняемые действия
}
while(условие);

С точки зрения реализации на ассемблере это самый легкий тип цикла:

.code
main proc
    mov eax, 1          ; помещаем в регистр EAX  число 1
do_while:
    ; выполняемые действия
    add eax, 1      ; действия цикла - EAX = EAX + 1
    cmp eax, 10      ; сравнивание значение регистра EAX с числом 10
    jl do_while     ; если EAX меньше 10, переход к метке do_while
    ret
main endp
end

Здесь помещаем в регистр EAX число 1. Затем после метки do_while производим действия цикла - увеличиваем значение регистра X0 на единицу. Далее сравниваем значение регистра EAX с числом 10. И если EAX меньше числа 10, то переходим обратно к метке do_while и повторяем действия цикла. Когда EAX станет равным 10, то произодейт выход из цикла.

цикл for

В ряде языков высокого уровня, например, есть циклическая конструкция for:

// бейсикоподобный синтаксис
FOR i = M TO N
    выполняемые действия
NEXT i
// сиподобный синтаксис
for(var i = M; i < N; i++)
{
    выполняемые действия
}

то есть есть некоторый счетчик i, и пока этот счетчик не достигнет значения N, будут выполняться некоторые действия цикла.

Реализация цикла for в ассемблере:

.code
main proc
    mov eax, 0   ; в EAX помещаем число - условный счетчик
 
for_start:              ; метка, на которую проецируется цикл
    cmp eax, 10         ; сравниваем с некоторым пределом
    jnl for_end         ; условие - если счетчик больше или равен пределу, выход из цикла
    ; выполняемые действия
    add eax, 1          ; действия цикла - увеличение счетчика
    jmp for_start       ; повторяем цикл
for_end:
    ret
main endp
end

В данном случае в EAX помещаем некоторое значение, которое будет выполнять роль счетчика цикла. Собственно цикл начинается с метки for_start. Сначала мы проверяем условие. Допустим, нам надо, чтобы цикл повторялся, пока в регистре EAX не окажется число 10. Если в EAX чило 10 или больше, то выходим из цикла - переходим к метке for_end. Если в EAX все еще меньше 10, то выполняем действия цикла - увеличиваем EAX на 1 и затем переходим обратно к метке for_start для повторения цикла.

Оптимизация циклов

Если мы посмотрим на ассемблерную реализацию циклов while и for, то увидим, что они в принципе похожи:

;цикл while
mov eax, 0
while_start:
    cmp eax, 10
    jnl while_end
    add eax, 1
    jmp while_start
while_end:


; цикл for
mov eax, 0  
for_start:              
    cmp eax, 10    
    jnl for_end  
    add eax, 1  
    jmp for_start 
for_end:

Такая реализация очень проста и понятна. Однако реализация цикла do_while гораздо эффективнее

    mov eax, 1
do_while:
    add eax, 1
    cmp eax, 10
    jl do_while

В отличие от while/for здесь отсутствует инструкция jmp, и сама конструкция будет теоретически выполняться быстрее. Но в циклах while/for нам важно выполнять определенные действия только при соблюдении некоторого условия. Соответственно это условие вначале надо проверить. Однако мы могли бы вынести действия цикла вперед:

.code
main proc
mov eax, 0  

jmp for_condition ; переходим к проверке условия

for_start:   
    add eax, 1           
for_condition:
    cmp eax, 10    
    jl for_start  ; если условие соблюдено переходим к метке for_start для выполнения действий

    ret
main endp
end

Здесь цикл начинается с перехода к метке for_condition, где проверяется условие - EAX должен быть меньше 10. Если это условие верно, переходим к метке for_start, где выполняем действия цикла. Затем опять проверяем условия и выполняем действия цикла, пока не дойдем до ситуации, когда EAX = 10.

Таким образом, инстркция jmp выполняется только один раз, а в остальном цикл for/while будет соответствовать циклу do-while.

Но и цикл do-while мы тоже можем до некоторой степени оптимизировать. Перепишем пример следующим образом:

.code
main proc
    mov eax, 9          ; помещаем в регистр EAX число 9
do_while:
    ; выполняемые действия
    sub eax, 1      ; действия цикла - EAX = EAX - 1
    jnz do_while    ; если флаг нуля установлен, то есть EAX=0, переход к метке do_while
    ret
main endp
end

Здесь идет обратный отсчет - от 9 до 1. И теперь значение в регистре EAX уменьшается на 1. Но цикл срабатывает столько же раз, сколько и в предыдущей реализации. Поскольку когда результат операции sub окажется равен 0 (то есть EAX = 0), то будет установлен флаг нуля. Соответственно мы можем проверить этот флаг, и если он еще не установлен, продолжить цикл. Таким образом, мы отбрасываем инструкцию cmp, поскольку нам не надо вручную сравнивать значения.

Если нам надо, чтобы EAX допускал значение 0 (от 9 до 0 - в этом случае флаг нуля мы так проверить не сможем), то мы можем проверять флаг знака, который устанавливается, если результат отрицательный:

.code
main proc
    mov eax, 9          ; помещаем в регистр EAX число 9
do_while:
    ; выполняемые действия
    sub eax, 1      ; действия цикла - EAX = EAX - 1
    jns do_while    ; если флаг знака SF =1, переход к метке do_while
    ret
main endp
end
Помощь сайту
Юмани:
410011174743222
Перевод на карту
Номер карты:
4048415020898850