Условные переходы и ветвления

Урок показывает, как из cmp и условного перехода собирается обычный if-else.

Условный переход прыгает только если выполнено условие на флагах; иначе исполнение идёт дальше по порядку.

Связка cmp + j-переход

Ветвление всегда состоит из двух шагов: сначала cmp выставляет флаги, потом условный переход их читает и решает, прыгать ли:

ПереходПрыгает, если
je / jzравно / ноль (ZF=1)
jne / jnzне равно (ZF=0)
jg / jlбольше / меньше (знаковые)
ja / jbбольше / меньше (беззнаковые)

Если-иначе на ассемблере

Рассмотрим C-код if (rax > 10) rbx = 1; else rbx = 0;. На ассемблере он раскладывается так:

    cmp rax, 10
    jg  bolshe       ; если rax > 10, прыгнуть в bolshe
    mov rbx, 0       ; ветка else
    jmp konec
bolshe:
    mov rbx, 1       ; ветка if
konec:

Обратите внимание: после ветки else стоит jmp konec, чтобы не «провалиться» в ветку if. Это типичная структура любого ветвления.

Как работает под капотом

Промоделируем эту логику на Python и проверим обе ветки:

def branch(rax):
    # cmp rax, 10 ; jg bolshe
    if rax > 10:        # ветка bolshe
        rbx = 1
    else:              # ветка else
        rbx = 0
    return rbx

print("rax=15 ->", branch(15))
print("rax=5  ->", branch(5))
print("rax=10 ->", branch(10))

Вывод:

rax=15 -> 1
rax=5  -> 0
rax=10 -> 0

Заметьте: при rax=10 результат 0, потому что jg прыгает только при строго большем. Выбор между jg и jge (больше или равно) — частая точка ошибок «на единицу».

Частые ошибки

  • Забыть jmp в конце ветки else. Тогда после else исполнение «провалится» в код ветки if.
  • Перепутать знаковые и беззнаковые переходы. jg/jl для знаковых, ja/jb для беззнаковых.
  • Вставить команду, портящую флаги, между cmp и переходом. Например, add перезапишет флаги, и переход сработает не так.

Итог

  • Ветвление = cmp (выставить флаги) + условный переход (прочитать флаги).
  • je/jne для равенства, jg/jl для знаковых, ja/jb для беззнаковых.
  • В конце одной ветки нужен jmp, чтобы пропустить другую.
  • Граница условия (jg против jge) — частый источник ошибок на единицу.
Проверьте себя
1. Из каких двух шагов состоит ветвление в ассемблере?
Amov и ret
Bcmp (выставить флаги) и условный переход (прочитать флаги)
Cpush и pop
Dadd и sub
2. Когда сработает je после cmp rax, rbx?
AЕсли rax больше rbx
BЕсли rax равно rbx (ZF=1)
CВсегда
DЕсли rax меньше rbx
3. Зачем в конце ветки else ставят jmp?
AЧтобы ускорить код
BЧтобы не «провалиться» в код ветки if
CЧтобы очистить флаги
DЭто необязательно и бесполезно