What is the role of the carry flag in Assembly language

What is the role of the carry flag in Assembly language

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

ArchitectureRegisterFlag NameBit Position
x86 (32-bit)EFLAGSCFBit 0
x86-64RFLAGSCFBit 0
ARM (AArch32/AArch64)CPSR / PSTATECPart 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:

FlagQuestion It AnswersRelevant 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

  1. Big integer arithmetic: Cryptographic libraries (RSA, elliptic curve operations) implement arbitrary-precision addition/multiplication using chains of ADC/ADCS across many register-widths.
  2. 64-bit arithmetic on 32-bit systems: Before 64-bit CPUs were ubiquitous, compilers generated exactly this ADD/ADC pattern to implement 64-bit integer addition using pairs of 32-bit registers.
  3. 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.
  4. 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/ADC pairs).
  • Be aware that ADC/SBB create 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_u64 in C) when available, since compilers can often schedule carry-chains more effectively than naive hand-written assembly.

Comparison: Carry Flag vs. Related Flags

FlagMeaningTypical Use
Carry Flag (CF/C)Unsigned overflow/borrowMulti-precision math, unsigned comparisons
Overflow Flag (OF/V)Signed overflowSigned arithmetic correctness
Zero Flag (ZF/Z)Result is exactly 0Equality checks
Sign Flag (SF/N)Result is negativeSigned 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/ADCS depends 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/DEC affect 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/JB on x86; B.CS/B.CC, B.HI/B.LO on ARM) specifically when reasoning about carry-flag-based logic.
  • Keep ADC/ADCS chains 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/ADCS and SBB/SBCS use 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
Total
1
Shares

Leave a Reply

Previous Post
Describe the purpose of the data segment in Assembly language programming

Describe the purpose of the data segment in Assembly language programming

Next Post
How are multi-byte data types represented in Assembly language

How Are Multi-Byte Data Types Represented in Assembly Language?

Related Posts