I didn’t fully appreciate the carry flag until I tried to add two 64-bit numbers together on a 32-bit system and got a result that was just… wrong. Not randomly wrong — consistently off by exactly the amount you’d expect if a carry from the low half never made it into the high half. That’s when the carry flag stopped being an abstract line in a manual and became something I genuinely needed to understand. This post covers what the carry flag is, how it’s set, how it enables multi-precision arithmetic, and how it differs from the closely related (and often confused) overflow flag.
What Is the Carry Flag?
The carry flag (CF on x86/x86-64, C on ARM) is a status bit that records whether an arithmetic operation produced a carry-out (for addition) or a borrow (for subtraction) beyond the width of the destination register, when treating the operands as unsigned numbers.
In other words: if you add two unsigned numbers and the true mathematical result doesn’t fit in the register width, the carry flag captures that “overflow” bit that got dropped.
Where the Carry Flag Lives
| Architecture | Register | Flag Name | Bit Position |
|---|---|---|---|
| x86 (32-bit) | EFLAGS | CF | Bit 0 |
| x86-64 | RFLAGS | CF | Bit 0 |
| ARM (AArch32/AArch64) | CPSR / PSTATE | C | Part of NZCV |
A Concrete Example
Let’s add two 8-bit unsigned numbers: 0xFF + 0x02.
11111111 (0xFF = 255)
+ 00000010 (0x02 = 2)
-----------
100000001 (9 bits needed, but register is only 8 bits wide)
The 8-bit register can only hold 00000001 (the low 8 bits), and the 9th bit — the actual carry-out — is captured in the carry flag. So after this addition: the register holds 0x01, and CF = 1, correctly signaling that the true unsigned sum (257) didn’t fit in 8 bits.
; x86-64 example
mov al, 0xFF
add al, 0x02 ; al becomes 0x01, CF is set to 1
jc overflow_occurred ; jump if carry flag is set
; ARM64 example
MOV W0, #0xFF
ADDS W0, W0, #2 ; W0 becomes 1, C flag is set
B.CS overflow_occurred ; branch if carry set (CS = Carry Set)
Carry Flag vs. Overflow Flag: The Classic Confusion
This is, without exaggeration, one of the most commonly misunderstood pairs of concepts in assembly programming. Both flags can be set by the exact same instruction, but they answer different questions:
| Flag | Question It Answers | Relevant For |
|---|---|---|
| Carry Flag (CF/C) | Did the result overflow if the operands are treated as unsigned? | Unsigned arithmetic, multi-precision math, pointer arithmetic |
| Overflow Flag (OF/V) | Did the result overflow if the operands are treated as signed (two’s complement)? | Signed arithmetic correctness |
The same bit pattern can simultaneously be a valid unsigned result with a carry, and an invalid signed result with an overflow, or vice versa — because “correctness” depends entirely on how you’re interpreting the bits.
Example Showing Both Flags in Action
mov al, 0x7F ; 127 unsigned, or +127 signed
add al, 0x01 ; al becomes 0x80
; CF = 0 (127 + 1 = 128, fits fine in unsigned 8-bit range 0-255)
; OF = 1 (as signed: +127 + 1 should be +128, but 0x80 as signed 8-bit is -128 -- overflow!)
This single example proves the point: the same addition sets CF to 0 (no unsigned problem) while setting OF to 1 (a very real signed problem) — because interpreting 0x80 as unsigned (128) is perfectly valid, but interpreting it as signed (-128) is a broken result of adding two positive numbers.
The Carry Flag’s Superpower: Multi-Precision Arithmetic
The carry flag’s most important real-world job is enabling arithmetic on numbers larger than a single register — a technique called multi-precision (or “bignum”) arithmetic, used in cryptography, arbitrary-precision math libraries, and compiler-generated 64-bit arithmetic on 32-bit systems.
x86: ADC and SBB (Add/Subtract With Carry)
; Add two 64-bit numbers using two 32-bit registers each (simulating 64-bit add on a 32-bit system)
; Number A: EDX:EAX (high:low)
; Number B: ECX:EBX (high:low)
add eax, ebx ; add low 32 bits, sets CF if there's a carry-out
adc edx, ecx ; add high 32 bits PLUS the carry from the previous addition
ADC (Add with Carry) is the instruction that makes chained, multi-word addition possible — it adds the carry-in from the previous operation as an extra +1 when needed, letting you “ripple” a carry across as many register-widths as your number requires.
ARM64: ADCS (Add with Carry, Setting Flags)
; Adding two 128-bit numbers stored across two 64-bit register pairs
ADDS X0, X2, X4 ; add low halves, sets carry flag
ADC X1, X3, X5 ; add high halves plus carry-in from previous ADDS
ARM provides the same conceptual capability via ADCS/ADC and SBCS/SBC (subtract with carry/borrow), used identically to chain arithmetic across multiple register-widths.
Internal Working: How ADC Uses the Carry Flag
sequenceDiagram
participant ALU as ALU
participant Flags as Flags Register (CF)
participant Reg as Destination Register
Note over ALU,Reg: Step 1 - Add low words
ALU->>ALU: Compute low_A + low_B
ALU->>Flags: Set CF = 1 if result overflowed register width
ALU->>Reg: Store low result
Note over ALU,Reg: Step 2 - Add high words with carry-in
ALU->>Flags: Read current CF value
ALU->>ALU: Compute high_A + high_B + CF
ALU->>Flags: Update CF based on this new addition
ALU->>Reg: Store high result
The critical detail is that ADC reads the carry flag before the addition (as an extra input bit) and then writes a new carry flag value based on this combined addition — allowing an arbitrary chain of ADC instructions to correctly propagate a carry across as many words as needed.
Practical Use Cases
- Big integer arithmetic: Cryptographic libraries (RSA, elliptic curve operations) implement arbitrary-precision addition/multiplication using chains of
ADC/ADCSacross many register-widths. - 64-bit arithmetic on 32-bit systems: Before 64-bit CPUs were ubiquitous, compilers generated exactly this
ADD/ADCpattern to implement 64-bit integer addition using pairs of 32-bit registers. - Checksum and hashing algorithms: Some checksum algorithms (like the Internet checksum used in IP headers) explicitly rely on carry propagation across word boundaries during summation.
- Pointer and address arithmetic bounds checking: Detecting unsigned overflow when computing buffer offsets, a real concern in security-sensitive code.
Debugging the Carry Flag
In GDB:
(gdb) p $eflags
(gdb) info registers eflags
eflags 0x203 [ CF IF ]
Seeing CF present in that bracketed flag list confirms the carry flag is currently set — extremely useful when stepping through hand-written or compiler-generated multi-precision arithmetic routines to verify carries propagate correctly.
Optimization Considerations
- Avoid unnecessary CF-dependent instruction chains where a wider native register type would eliminate the need for multi-precision tricks entirely (e.g., just using 64-bit registers directly on a 64-bit system instead of emulating 64-bit math with 32-bit
ADD/ADCpairs). - Be aware that
ADC/SBBcreate a data dependency on the previous instruction’s flags, which can occasionally limit instruction-level parallelism in extremely tight, carry-chained loops — though modern CPUs handle flag dependencies quite efficiently via internal renaming. - Prefer compiler intrinsics for bignum math (e.g.,
_addcarry_u64in C) when available, since compilers can often schedule carry-chains more effectively than naive hand-written assembly.
Comparison: Carry Flag vs. Related Flags
| Flag | Meaning | Typical Use |
|---|---|---|
| Carry Flag (CF/C) | Unsigned overflow/borrow | Multi-precision math, unsigned comparisons |
| Overflow Flag (OF/V) | Signed overflow | Signed arithmetic correctness |
| Zero Flag (ZF/Z) | Result is exactly 0 | Equality checks |
| Sign Flag (SF/N) | Result is negative | Signed comparisons |
Common Mistakes
- Using the overflow flag (
JO/B.VS) when you actually meant to check unsigned overflow (JC/B.CS), or vice versa — this is the single most common flag-related bug in assembly. - Forgetting that
ADC/ADCSdepends on the carry flag set by the immediately preceding instruction — inserting an unrelated instruction in between can silently corrupt a multi-precision addition chain. - Assuming
INC/DECaffect the carry flag on x86 — they deliberately do not, precisely so they can be used inside carry-chains without disturbing an in-progress multi-word operation.
Best Practices
- Use unsigned condition codes (
JC/JNC,JA/JBon x86;B.CS/B.CC,B.HI/B.LOon ARM) specifically when reasoning about carry-flag-based logic. - Keep
ADC/ADCSchains free of intervening flag-modifying instructions to guarantee correct carry propagation. - When implementing bignum arithmetic, favor well-tested compiler intrinsics or established libraries over fully hand-rolled carry-chains unless you have a specific, measured performance reason to do otherwise.
FAQs
Is the carry flag the same as the overflow flag? No — they answer different questions about the same arithmetic result: CF is about unsigned overflow/borrow, OF is about signed overflow. They can differ or agree depending on the specific values involved.
Which instructions read the carry flag as an input? ADC/SBB on x86, and ADCS/SBCS/ADC/SBC on ARM, all read the current carry flag as an extra input bit, in addition to producing an updated carry flag as output.
Does the carry flag matter on 64-bit systems where I rarely need bignum math? Yes — it’s still used for compiler-generated arithmetic on wide types (__int128), cryptographic library implementations, and any unsigned overflow detection logic, even if you personally never write ADC by hand.
Summary and Key Takeaways
- The carry flag records unsigned overflow (on addition) or borrow (on subtraction) beyond the width of the destination register.
- It is distinct from the overflow flag, which tracks signed arithmetic correctness — the two can and do disagree depending on the operands.
ADC/ADCSandSBB/SBCSuse the carry flag to chain arithmetic across multiple register-widths, enabling multi-precision math.- Understanding the carry flag is essential for cryptography, bignum libraries, low-level checksum algorithms, and correct unsigned comparisons.
References
- Intel® 64 and IA-32 Architectures Software Developer’s Manual, Volume 1, Chapter 3 (EFLAGS, Carry Flag definition)
- AMD64 Architecture Programmer’s Manual, Volume 1
- Arm® Architecture Reference Manual for A-profile architecture (Condition flags, ADC/ADCS instructions)
- GNU Assembler (GAS) documentation, condition code and carry-related mnemonics
