Lesson 1 of 10
How CPUs Actually Work
Before writing any code, you'll build a simple picture of how a CPU runs a program: registers, , and the fetch-decode-execute cycle. The rest of the track builds on this.
A CPU is a machine that reads instructions from memory one at a time, executes them, and moves to the next. An instruction might be "add two numbers," "copy a value from memory," or "jump to a different instruction." Everything your computer does is built from long sequences of these small operations.
Registers are small, fast storage locations inside the CPU itself. x86-64 has 16 general-purpose registers: rax, rbx, rcx, rdx, rsi, rdi, rbp, rsp, and r8–r15. Each holds 64 bits (8 ). Operations on registers are the fastest thing the CPU can do — no memory access required.
; x86-64 general-purpose registers and their conventional uses:
; rax — accumulator: return values, arithmetic results
; rbx — base: callee-saved (must preserve across function calls)
; rcx — counter: loop counters, 4th argument
; rdx — data: I/O, 3rd argument
; rsi — source index: string source, 2nd argument
; rdi — destination index: string dest, 1st argument
; rbp — base pointer: stack frame base (callee-saved)
; rsp — stack pointer: always points to top of stack
; r8–r11 — 5th–8th arguments, caller-saved
; r12–r15 — callee-saved scratch registers
; Each 64-bit register has sub-register aliases:
; rax = 64-bit (the full register)
; eax = lower 32 bits
; ax = lower 16 bits
; al = lower 8 bits
; ah = bits 8-15
; Example: after mov rax, 0x0000000100FF
; rax = 0x0000000100FF
; eax = 0x000100FF
; ax = 0x00FF
; al = 0xFF
; ah = 0x00Memory is a huge of bytes, each with an address (a 64-bit number on x86-64). To use data in memory, you load it into a register, operate on it, then store it back. The is a region of memory that grows downward — rsp points to the current top. The heap is where malloc lives. Code lives in the text segment.
; Memory layout of a running program (low address at top):
;
; 0x0000... ──────────────────────
; │ text segment │ your compiled code (read + execute)
; ├──────────────────────┤
; │ data segment │ initialized globals (read + write)
; ├──────────────────────┤
; │ bss segment │ uninitialized globals (zero-filled)
; ├──────────────────────┤
; │ heap │ grows upward (malloc/free)
; │ ↓ │
; │ (free space) │
; │ ↑ │
; │ stack │ grows downward
; 0xFFFF... ──────────────────────
;
; rsp always points to the last pushed value on the stack.
; push rax → rsp -= 8, then [rsp] = rax
; pop rax → rax = [rsp], then rsp += 8Common question
Does the processor understand the code I write?
A processor repeats one : fetch the instruction at the program counter, decode it, run it, move on. The instructions move data between registers and memory, do arithmetic and logic, compare values and set flags, and jump based on those flags. Everything a language gives you is compiled down to that.
Learning a little assembly changes how you read higher-level code. Stack frames explain limits and stack overflows; registers explain why passing a few is cheap; flags explain why branch-heavy code can be slow; and system calls show exactly where the takes over.
Source: Compile any program with -S and read the output: the names are gone, and what is left is registers, memory and jumps.
Common mix-ups
Each of these sounds reasonable. Think about why it is wrong, then open the explanation.
The CPU runs the program, so it must understand its and .
Assembly is faster than a compiled language because it's closer to the machine.
A function call is a basic operation the hardware provides.
Tip: use the ← and → keys to move between lessons.