Introduction: The Moment Your Program Starts Making Decisions
Let me tell you about the exact moment I fell in love with assembly language. It wasn’t when I wrote my first “Hello, World” program. It wasn’t when I figured out how registers work. It was when I wrote my first conditional branch — when my program actually decided something, when it went down one path instead of another based on a calculation I’d written.
That’s when assembly stopped being a glorified calculator and started being a programming language.
In this article — the eighth in our series — we’re going to master branch programming. We’ll dive deep into the EFLAGS register, understand conditional jumps (all of them), learn why signed vs unsigned comparisons will trip you up, and explore advanced topics like branch prediction and conditional moves. We’ll also build a complete bubble sort program that uses conditional branches.
This is where assembly programming gets interesting. Strap in.
The EFLAGS Register Refresher: Which Flags Matter for Branching
Let’s start with the foundation of all branching: the EFLAGS register. (In 64-bit mode, it’s called RFLAGS, but the lower 32 bits are the same as EFLAGS.)
The EFLAGS register is a 32-bit register where each bit (or group of bits) represents a condition resulting from the last arithmetic, logic, or comparison operation. When you execute a CMP, ADD, TEST, or most other instructions, the CPU automatically updates the relevant flags.
Here’s the layout of the EFLAGS register (only the flags relevant to branching):
31 30 29 28 27 26 25 24 23 22 21 20 19 18 17 16
┌───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┐
│ 0 │ 0 │ 0 │ 0 │ 0 │ 0 │ 0 │ 0 │ 0 │ 0 │ID │VIP│VIF│AC │VM │RF │
└───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┘
15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
┌───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┐
│ 0 │NT │IOPL│IOPL│OF │DF │IF │TF │SF │ZF │ 0 │AF │ 0 │PF │ 1 │CF │
└───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┘
The flags that matter for branching are:
ZF (Zero Flag) — Bit 6
The Zero Flag is set to 1 if the result of the last operation was zero. Otherwise, it’s cleared to 0.
MOV EAX, 5
SUB EAX, 5 ; EAX = 0, so ZF = 1
INC EAX ; EAX = 1, so ZF = 0
Used by: JE/JZ (Jump if Equal/Jump if Zero), JNE/JNZ (Jump if Not Equal/Jump if Not Zero)
SF (Sign Flag) — Bit 7
The Sign Flag is set to the most significant bit (MSB) of the result. For 32-bit values, that’s bit 31. If the result is negative (MSB = 1), SF = 1. If the result is positive or zero (MSB = 0), SF = 0.
MOV EAX, -5 ; EAX = 0xFFFFFFFB, MSB = 1, so SF = 1
ADD EAX, 10 ; EAX = 5, MSB = 0, so SF = 0
Used by: JL/JNGE (Jump if Less), JG/JNLE (Jump if Greater), and other signed conditional jumps.
OF (Overflow Flag) — Bit 11
The Overflow Flag is set when the last operation caused signed overflow. That is, when the result is too large (or too small) to fit in the destination operand.
MOV AL, 127 ; AL = 127 (largest signed 8-bit positive number)
INC AL ; AL = -128 (wrap-around!), OF = 1
Used by: JO (Jump if Overflow), JNO (Jump if No Overflow), and signed conditional jumps (JL, JG, etc.) which consider OF.
CF (Carry Flag) — Bit 0
The Carry Flag is set when the last operation caused an unsigned overflow. That is, when the result is too large to fit in the destination operand as an unsigned number.
MOV AL, 0xFF ; AL = 255 (largest unsigned 8-bit number)
INC AL ; AL = 0, CF = 1 (carry out of bit 7)
Used by: JC (Jump if Carry), JNC (Jump if No Carry), and unsigned conditional jumps (JB, JA, etc.).
PF (Parity Flag) — Bit 2
The Parity Flag is set if the number of 1-bits in the least significant byte of the result is even. Otherwise, it’s cleared.
MOV AL, 3 ; AL = 00000011b (two 1-bits, even parity)
ADD AL, 1 ; AL = 00000100b (one 1-bit, odd parity), PF = 0
Used by: JP/JPE (Jump if Parity Even), JNP/JPO (Jump if Parity Odd). Rarely used in high-level programming, but useful in some low-level contexts (cryptography, error detection).
AF (Auxiliary Carry Flag) — Bit 4
The Auxiliary Carry Flag is set on a carry or borrow from bit 3 to bit 4 of the result. It’s used for BCD (Binary Coded Decimal) arithmetic, which is obsolete, so you’ll rarely encounter it.
A Quick Example: How Flags Are Set
Let me show you how flags are set after various operations:
.DATA
resultMsg DB 'Result: ', 0
zeroMsg DB 'ZF=1 (result was zero)', 10, 0
notZeroMsg DB 'ZF=0 (result was not zero)', 10, 0
.CODE
main PROC
; Example 1: Result is zero
MOV EAX, 5
SUB EAX, 5 ; EAX = 0
; Flags: ZF=1, SF=0, CF=0, OF=0
JZ wasZero ; This will jump (ZF=1)
JMP notZero
wasZero:
PUSH OFFSET zeroMsg
CALL printf
ADD ESP, 4
JMP done
notZero:
PUSH OFFSET notZeroMsg
CALL printf
ADD ESP, 4
done:
PUSH 0
CALL exit
main ENDP
END main
Viewing the Flags in a Debugger
When you’re debugging, you’ll often want to inspect the flags. Here’s how:
In WinDbg:
r ; Displays all registers AND flags
; Look for "nz" (not zero, ZF=0), "zr" (zero, ZF=1), "ng" (negative, SF=1), etc.
In GDB (for Linux assembly):
info registers ; Displays all registers AND flags
In DEBUG.exe (DOS):
R ; Displays all registers AND flags<br>; Look for "NZ", "ZR", "NG", "PL", etc.



