Skip to main content

x86-64 Registers and Instructions

Overview

Every x86-64 instruction operates on a small set of named storage locations — registers — and/or memory. Unlike a high-level language with an unlimited number of named variables, an x86-64 CPU exposes only 16 general-purpose registers, plus a handful of special-purpose ones. Understanding their names (and the historical sub-register naming that trips up almost everyone the first time) and the handful of instruction categories that combine to express any program is the foundation for reading any disassembly.

Core Concepts

TermMeaning
General-purpose register (GPR)One of 16 CPU-internal 64-bit storage slots (rax-r15) directly addressable by instructions.
Sub-registerA name referring to the low 8, 16, or 32 bits of a 64-bit register — not a separate register.
Addressing modeHow an operand's location is specified: immediate constant, register, plain memory address, or memory with a displacement/index.
Flags register (rflags)Holds status bits (zero, sign, carry, overflow, ...) set by arithmetic/compare instructions and read by conditional jumps.
ImmediateA literal constant value encoded directly into the instruction, e.g. the 5 in mov eax, 5.
MnemonicThe human-readable instruction name (mov, add, jmp) that assembles to a specific opcode.

The General-Purpose Registers

x86-64 has 16 general-purpose 64-bit registers: rax, rbx, rcx, rdx, rsi, rdi, rbp, rsp, and r8-r15. The first eight carry names inherited from 16-bit and 32-bit x86 history; the last eight were added when AMD extended x86 to 64 bits and are named uniformly.

The historical sub-register naming (a common confusion point)

eax, ax, and al are not separate registers — they are all views into the same physical rax register, just narrower slices of its low bits. This naming survives from the 8086 (16-bit ax) through the 80386 (32-bit eax, "extended ax") to x86-64 (64-bit rax, "register ax"). Writing to a narrower view leaves the rest of the register's upper bits either unchanged or zeroed, depending on the width — a frequent source of subtle bugs and disassembly confusion:

  • Writing a 32-bit sub-register (e.g. mov eax, 1) zero-extends and clears the upper 32 bits of the full 64-bit register — this is a special case baked into the ISA.
  • Writing a 16-bit or 8-bit sub-register (e.g. mov al, 1) leaves the rest of the register's bits untouched — only those specific bits change.
64-bit32-bit16-bit8-bit (low)Conventional role (System V)
raxeaxaxalReturn value / accumulator
rbxebxbxblCallee-saved, general purpose
rcxecxcxcl4th argument; loop counter (historically)
rdxedxdxdl3rd argument
rsiesisisil2nd argument; source index (historically)
rdiedididil1st argument; destination index (historically)
rbpebpbpbplCallee-saved; conventionally the stack-frame base pointer
rspespspsplThe stack pointer — always points at the top of the stack
r8-r15r8d-r15dr8w-r15wr8b-r15b5th/6th arguments (r8, r9); rest general purpose
Why no r8h?

The ah/bh/ch/dh "high byte of the low 16 bits" names are a legacy quirk of the original four registers only. r8-r15 (and the newer sil/dil/bpl/spl byte names) never got equivalent high-byte aliases, and mixing ah-style registers with any instruction using a REX prefix (needed to address r8-r15 or 64-bit operands) is disallowed by the encoding.

Instruction Categories

Nearly every x86-64 instruction falls into one of a few categories:

CategoryExamplesPurpose
Data movementmov, lea, push, popCopy values between registers, memory, and the stack.
Arithmetic / logicadd, sub, imul, idiv, and, or, xor, shl, shrCompute new values; update the flags register as a side effect.
Comparisoncmp, testCompute a result only to set flags (result itself is discarded).
Control flowjmp, je/jz, jne, jl, jle, jg, jge, call, retUnconditionally or conditionally change which instruction runs next.

cmp a, b internally computes a - b and sets flags (zero, sign, carry, overflow) without storing the result; a following conditional jump (e.g. jle, "jump if less-or-equal") reads those flags to decide whether to branch. This flags-then-branch pattern is how every if, loop condition, and comparison operator in a higher-level language ends up expressed in assembly — see Reading Disassembly for it in action.

Addressing Modes

An instruction's operand can be specified several ways; x86-64's memory operands can combine a base register, an index register, a scale factor, and a constant displacement in a single instruction:

mov eax, 5 ; immediate: a literal constant
mov eax, ebx ; register: value lives in a register
mov eax, [rbx] ; memory: address is the value in rbx
mov eax, [rbx + 8] ; memory + displacement: address is rbx + 8
mov eax, [rbx + rcx*4 + 8] ; base + index*scale + displacement (e.g. arr[i])

The last form is exactly how array indexing (arr[i]) compiles: rbx holds the array's base address, rcx holds the index i, 4 is sizeof(int), and 8 might be a fixed offset into a struct.

Practical Usage

Compile a tiny C function and inspect the sub-register usage directly:

// widen.c
unsigned long widen(unsigned int x) {
return (unsigned long)x + 1;
}
gcc -S -O2 -masm=intel widen.c -o widen.s

Look for a mov eax, edi — writing the 32-bit edi sub-register as part of moving the incoming argument, which the ISA guarantees zero-extends into the full 64-bit rdi/rax before the addition, exactly the "writing a 32-bit sub-register clears the upper half" rule above.

Edge Cases & Pitfalls

8/16-bit sub-register writes don't clear the upper bits

Unlike 32-bit writes, mov al, 0 only changes the lowest byte of rax — the other 56 bits keep whatever garbage was there before. Code that reads eax or rax right after only setting al without having first cleared or fully set the full register is a classic uninitialized-value bug.

Don't confuse a sub-register with an independent variable

eax and al are the same storage as rax, just narrower views. Assembly beginners sometimes assume rax and eax can hold two different live values at once — they cannot; writing one always affects the other's bits.

  • Not every register is safe to clobber. By System V AMD64 ABI convention, some registers are caller-saved (may be destroyed by any call) and others are callee-saved (a called function must restore them before returning) — see Calling Conventions & the Stack for the full breakdown.
  • rsp should generally only be modified through push/pop/call/ret or deliberate, aligned arithmetic — an off-by-one adjustment corrupts the entire stack for the rest of the function.

Comparisons

Aspectx86-64 (this page)ARM64 (AArch64)
GPR count16 (rax-r15)31 (x0-x30)
Sub-register viewsYes — rax/eax/ax/al share storageYes — x0/w0 share the low 32 bits, but only two widths
Memory-operand arithmeticInstructions can read/modify memory directly (add [rbx], 1)Load/store only — ALU ops never touch memory directly
Flags registerrflags, implicitly updated by many instructionsNZCV, updated only by instructions with an explicit s suffix (e.g. adds)

References

Books & Videos

  • Randall Hyde, The Art of Assembly Language — a thorough, beginner-friendly treatment of x86 registers and instruction categories.
  • Bryant & O'Hallaron, Computer Systems: A Programmer's Perspective — Chapter "Machine-Level Representation of Programs" covers x86-64 registers and instructions from a C programmer's perspective.