Nobody writes application code in raw assembly anymore, but understanding it demystifies what every compiler is actually doing on your behalf, and it’s genuinely useful the moment you need to read a disassembly during debugging or reverse engineering.
Registers: The CPU’s Scratch Space
Before touching memory, the CPU works with a small set of extremely fast on-chip registers. On x86-64, the general-purpose ones include rax, rbx, rcx, rdx, plus rsp (stack pointer) and rbp (base pointer).
A Minimal Program
section .data
result dq 0
section .text
global _start
_start:
mov rax, 5 ; move 5 into register rax
add rax, 10 ; rax = rax + 10 -> rax now holds 15
mov [result], rax ; store the result in memory
; exit(0) syscall
mov rax, 60
xor rdi, rdi
syscall
nasm -f elf64 add.asm -o add.o ld add.o -o add ./add
Seeing What Your Compiler Actually Generates
The fastest way to build intuition is comparing simple C against its compiled output — Compiler Explorer (godbolt.org) does this interactively, but you can do it locally too:
echo 'int add(int a, int b) { return a + b; }' > add.c
gcc -O2 -c add.c -o add.o
objdump -d add.o
# lea eax, [rdi + rsi] <- the compiler folded add + move into one instruction
# ret
Why This Is Still Worth Knowing
Understanding registers and the stack directly explains concepts that otherwise feel abstract in higher-level languages — function call overhead, why recursion has a depth limit, why certain optimizations (like inlining) exist, and how a debugger like gdb can actually inspect a running program’s state at the machine level.