roadmap
week 2 · day 9

cmp, Conditional Jumps & if

How a decision becomes a jump

Easy 30 min 180 xp
After this you can
  • Explain what cmp does and why it changes no register
  • Turn an if-statement into a cmp plus a conditional jump, by hand
  • Choose the right jump (je/jne/jg/jl/ja/jb) and say why signed and unsigned differ
  • Read an if/else in disassembly and reconstruct the C condition

There is no "if" in a CPU

A processor cannot branch on a condition the way you write if (x > 0). It can do exactly two relevant things: arithmetic that sets flags, and jumps that read flags. An if is those two glued together. Once you see that, every conditional in every language becomes readable.

cmp: ask a question, keep your data

Yesterday you met it: `cmp a, b` computes a - b, sets ZF/SF/CF/OF, and *discards the result*. It's a subtraction whose only purpose is the receipt. You use it to *ask a question about two values without disturbing them*:

  • cmp rax, rbx then the flags encode how rax compares to rbx.
  • If rax == rbx, the subtraction is 0 → ZF=1.
  • If rax < rbx (unsigned), the subtraction borrows → CF=1.
  • Signed comparisons read SF and OF together.

The jump reads the receipt

A conditional jump looks at the flags and either jumps to a label or falls through. Its mnemonic tells you the condition it tests:

JumpJumps if…FlagsSignedness
je / jzequal (zero)ZF=1either
jne / jnznot equalZF=0either
jggreatersigned >signed
jllesssigned <signed
jge / jlegreater/less or equalsignedsigned
jaaboveunsigned >unsigned
jbbelowunsigned <unsigned
Key idea
Signed and unsigned comparisons use different jumpsjg/jl for signed, ja/jb for unsigned — even though the cmp before them is identical. Why: the *bits* don't say whether 0xFF means 255 or −1 (day 1 again). jg reads SF and OF (the signed verdict); ja reads CF and ZF (the unsigned verdict). Pick the wrong family and −1 > 5 can come out "true." This is a real, common bug — and a real exploit primitive later.

An if, compiled by hand

Take this C:

check.cc
1if (x > 10) {
2 y = 1;
3} else {
4 y = 0;
5}

Assuming x is a signed int in eax and y lives in ecx, the machine version is:

the same if, in assemblyasm
1 cmp eax, 10 ; compute x - 10, set flags (x is signed → use signed jump)
2 jg .then ; if x > 10, jump to the 'then' block
3 mov ecx, 0 ; else: y = 0
4 jmp .end
5.then:
6 mov ecx, 1 ; then: y = 1
7.end:

Read it top to bottom: compare, conditional jump to one branch, the other branch falls through, and an unconditional jmp skips past the branch not taken. That skeleton — cmp, conditional jump, two blocks, a joining label — *is* an if/else. Every one you've ever written looks like this underneath.

Predict first
x is an unsigned int in eax holding 0xFFFFFFFF (which is 4294967295 unsigned, or −1 if you misread it as signed). The code does cmp eax, 10 then `jg .then`. Does it jump? Should it?
Note
To read an if in disassembly: find the cmp (or test), see which conditional jump follows, and translate. jg/jl/jge/jle → the C used signed types; ja/jb/jae/jbe → unsigned types. The jump family tells you the *sign* of the variables, not just the comparison.
finished reading?
Your task, you write the code

Compile an if in your head, then check

Write branch.c containing a function that returns 1 if its int argument is greater than 10 and 0 otherwise. FIRST, on paper, write the cmp + conditional-jump assembly you expect (using a signed jump, since int is signed). THEN compile with `gcc -O0 -g`, disassemble with objdump -d -M intel, and compare your hand-written version to the compiler's. Finally change the parameter type to `unsigned` and confirm the jump mnemonic changes from a signed one (jg) to an unsigned one (ja).

deliverable: branch.c (+ your hand-written assembly)
build & run
$ gcc -O0 -g branch.c -o branch
$ objdump -d -M intel branch
self-review before running
  • Your hand-written cmp + jump matches the shape the compiler produced
  • The signed version uses jg (or jle); the unsigned version uses ja (or jbe)
  • You can explain why the mnemonic changed when only the type changed
  • You can point at the unconditional jmp that skips the untaken branch
stretchWrite `if (a == b) ... else ...` and find the je/jne. Then chain two conditions with && and observe how the compiler lays out two cmps and a short-circuit jump — reconstruct the control flow as a little diagram.

Self-check

01What does cmp change?
02You are comparing two UNSIGNED integers. Which jump means 'greater'?
03An if/else compiles to roughly:
04Why do signed and unsigned comparisons need different jump instructions after an identical cmp?
0/4 correct · 0/4 checked