Unconditional and Conditional Jumps
jmp versus je, jne, jl, jg and the rest, and which flags each one reads.
Changing Where Execution Goes
Normally the CPU runs one instruction after another, top to bottom. A jump breaks that flow: it sets the instruction pointer to a label somewhere else, so the next instruction executed is the one at that label rather than the one physically below. Every if, else, for, while, and switch you have ever written eventually becomes some arrangement of jumps. This lesson catalogues them.
There are two kinds: the unconditional jump, which always goes, and the conditional jumps, which go only when the flags say so.
jmp: the Unconditional Jump
jmp .L3 means "continue from label .L3, no questions asked". It always transfers control. You saw it in the previous lesson, on the line that skips over the else block:
mov eax, 1
jmp .L3
.L2:
mov eax, 0
.L3:
Once eax has been set to 1, the jmp .L3 leaps over the mov eax, 0 so that the "return 0" path is not also executed. An unconditional jump is how the compiler stitches together blocks that must not fall into one another.
Conditional Jumps Read the Flags
A conditional jump takes the branch only if the flags are in a particular state, the state left behind by a preceding cmp or test. If the condition is not met, the CPU simply falls through to the next instruction. The mnemonic spells out the condition:
| Jump | Takes branch when... | Reads |
|---|---|---|
je / jz |
equal / zero (ZF set) |
ZF |
jne / jnz |
not equal / not zero (ZF clear) |
ZF |
js |
result was negative | SF |
jns |
result was not negative | SF |
je and jz are literally the same instruction with two names: "jump if equal" and "jump if zero" describe the same flag (ZF), because cmp a, b sets ZF when a - b is zero, i.e. when a == b. Use whichever name reads more naturally for the surrounding code.
Two Families: Signed and Unsigned
The comparisons that get interesting are the orderings, less-than and greater-than. Here x86-64 has a fork in the road: there are two complete sets of conditional jumps, one for values the compiler is treating as signed and one for unsigned. They exist because "less than" means different things for the two interpretations. The bit pattern 0xFFFFFFFF is -1 as a signed int but 4294967295 as an unsigned one, so a single set of jumps could not possibly serve both.
The signed family uses the words less and greater:
| Jump | Branch when (signed) |
|---|---|
jl / jnge |
less than (<) |
jle / jng |
less than or equal (<=) |
jg / jnle |
greater than (>) |
jge / jnl |
greater than or equal (>=) |
The unsigned family uses the words below and above:
| Jump | Branch when (unsigned) |
|---|---|
jb / jnae |
below (<) |
jbe / jna |
below or equal (<=) |
ja / jnbe |
above (>) |
jae / jnb |
above or equal (>=) |
This is one of the most useful tells in all of assembly reading. If you see jl, jg, jle, jge, the compiler is treating the values as signed. If you see jb, ja, jbe, jae, it is treating them as unsigned. The same C++ comparison operator can compile to either, depending on the types involved, so the jump mnemonic quietly tells you the signedness of the data.
Under the hood, the signed jumps inspect SF together with OF, while the unsigned jumps inspect CF (and ZF for the "or equal" variants). You rarely need to trace the exact flag logic; recognising which family a jump belongs to is what matters.
Seeing the Signed Family in Action
Here is a variant of the classify function from the last lesson. This time the source compares score >= 50:
int classify(int score)
{
if (score >= 50)
return 1;
else
return 0;
}
classify(int):
push rbp
mov rbp, rsp
mov DWORD PTR -4[rbp], edi
cmp DWORD PTR -4[rbp], 49
jle .L2
mov eax, 1
jmp .L3
.L2:
mov eax, 0
.L3:
pop rbp
ret
Two things are worth noticing. First, the jump is jle, from the signed family, because score is an int. Second, the compiler rewrote the test: the source asked score >= 50, but the assembly compares against 49 and jumps with jle (less than or equal). It inverted the condition so the "false" path is the one that branches away. score >= 50 is the opposite of score <= 49 for integers, so jumping to the return 0 block on score <= 49 produces identical behaviour. Compilers do this rephrasing constantly; the jump you see is often the negation of the comparison you wrote.
A Quick Reference for Reading
When you meet a conditional jump, ask three questions:
- What flag(s) does it read? That tells you what
cmp/testwas really checking. - Signed or unsigned? less/greater means signed; below/above means unsigned.
- Is it the inverted condition? Compilers often branch on the opposite of the source test to skip a block.
Answer those and any branch becomes readable, no matter how the C++ above it was written.
Key Takeaways
jmpis unconditional: it always transfers control, typically to skip over a block.- Conditional jumps branch only when the flags match; otherwise execution falls through.
je/jzandjne/jnztestZF(equality / zero);js/jnstestSF(sign).- There are two ordering families: signed (
jl,jle,jg,jge) and unsigned (jb,jbe,ja,jae). - The mnemonic reveals signedness: less/greater = signed, below/above = unsigned.
- Compilers frequently branch on the inverted condition, comparing against an adjusted constant to skip the false path.
Create an account to track your progress and access interactive exercises. Already have one? Sign in.
Unconditional and Conditional Jumps - Quiz
Test your understanding of the lesson.
Lesson Discussion
Share your thoughts and questions
No comments yet. Be the first to share your thoughts!