Arithmetic & the FLAGS Register
add, sub, and the 4 bits that decide every branch
- › Do arithmetic in assembly: add, sub, inc, dec, neg, imul
- › Explain the four condition flags ZF, SF, CF, OF and what each records
- › Derive which flags an operation sets, by hand, from the result and the two's-complement rules
- › Say why the same add serves both signed and unsigned math, and where each overflows
Every operation leaves a receipt
Arithmetic in assembly is what you'd guess: add rax, rbx does rax = rax + rbx; sub, inc,
dec, neg, imul do the obvious. That part is easy. The part that *matters* — the part that
makes decisions possible — is that every arithmetic instruction quietly writes a receipt into a
special register called RFLAGS. Four bits of that receipt drive every branch you'll ever write.
The CPU has no "if." An if is: do some arithmetic, read the receipt, jump based on it. So today is
really the foundation of *all* control flow.
The four flags that matter
RFLAGS has many bits; four decide branches. Each records one fact about the *result* of the last operation:
| Flag | Name | Set when… | Reads the question |
|---|---|---|---|
| ZF | Zero | the result is exactly 0 | were they equal? (after cmp) |
| SF | Sign | the result's top bit is 1 | is the (signed) result negative? |
| CF | Carry | an unsigned add carried out / sub borrowed | did UNSIGNED math overflow? |
| OF | Overflow | the signed result won't fit | did SIGNED math overflow? |
Notice the split at the bottom: CF is the unsigned overflow flag; OF is the signed overflow flag. The CPU can't know whether *you* meant a byte to be 0–255 or −128–127 — the bits are the same (day 1). So it computes *both* verdicts every time and hands you both flags; your choice of which branch instruction to use later decides which one you're asking about.
Watch the flags fall out of one subtraction
Take an 8-bit sub al, bl with al = 0x05, bl = 0x05:
0000 0101 (5)
- 0000 0101 (5)
-----------
0000 0000 (0) → ZF=1 (zero!) SF=0 (top bit 0) CF=0 (no borrow) OF=0Result 0 → ZF=1. That single flag is how if (a == b) works: subtract, and if ZF is set they
were equal. Now the interesting cases — overflow. Recall day 2: an 8-bit *signed* value maxes at
0x7F = +127.
0111 1111 (unsigned 127 / signed +127)
+ 0000 0001 (1)
-----------
1000 0000 → SF=1 (top bit set) ZF=0 CF=0 (no unsigned carry: 127+1=128 fits in 8 bits unsigned)
OF=1 (SIGNED overflow: +127 + 1 should be +128, but that can't fit; bits say -128)A rule you can apply, not memorize
- ZF = "is the result all zeros?" — set it by looking at the result.
- SF = "is the top bit of the result 1?" — copy the sign bit.
- CF (for add) = "did a 1 carry out past the top bit?" i.e. did the true unsigned sum need one
- more bit than fits. For sub, "did you have to borrow?"
- OF = "do the two inputs have the same sign but the result has the opposite sign?" That's the
- signature of signed overflow — two positives making a negative, or two negatives making a positive.
You never memorize a table; you read the result and apply these four questions.
add al, bl with al = 0x80 (signed −128) and bl = 0x80 (signed −128). Predict the
result byte and all four flags (ZF, SF, CF, OF).1 mov al, 0x7f2 add al, 1 ; al = 0x80 ; OF=1 (signed overflow), SF=13 mov bl, 0xff4 add bl, 1 ; bl = 0x00 ; ZF=1, CF=1 (unsigned wrap)5 sub bl, 1 ; bl = 0xff ; CF=1 (borrow), SF=1a - b, sets the
flags, and throws the result away. And `test a, b` does the same for a bitwise AND. These are how
you ask a question without disturbing your data — the star of tomorrow's lesson.Make the flags visible
In flags.asm, perform three 8-bit operations you predict the flags for: (a) add that causes an unsigned wrap, (b) add that causes a signed overflow, (c) a sub that results in zero. For EACH, before running, write on paper your predicted ZF/SF/CF/OF using the four questions from this lesson. Then step through in gdb and read `info registers eflags` after each, confirming your predictions. You only need add/sub/mov and the flag rules taught here.
$ nasm -f elf64 flags.asm -o flags.o && ld flags.o -o flags$ gdb ./flags # break _start, stepi, then: info registers eflags- › You predicted all four flags for each op BEFORE running
- › Your unsigned-wrap case shows CF=1 and your signed-overflow case shows OF=1
- › Your zero result shows ZF=1
- › You can state the difference between CF and OF in one sentence