Inside the CPU
Registers, the fetch-execute loop, and memory as one giant array
You've seen how the machine represents numbers. Now meet the thing that acts on them. A CPU sounds mysterious, but underneath it does exactly one thing, forever, a billion times a second — and once you see that one thing, the whole processor stops being magic. This subject shows you the engine (a simple loop), the workbench it computes on (a handful of registers), and the warehouse it pulls from (memory as one enormous array of boxes). By the end you'll open a running program and watch its registers and memory change with your own eyes.
- › Describe what a CPU does as a single simple loop (fetch, decode, execute)
- › Name the x86-64 registers and explain what registers are for
- › Picture memory as one giant byte-addressed array, and explain endianness
- › Inspect a running program's registers and memory in a debugger
1 · The loop that is a computer
Here is the entire engine of a processor, and it never changes: fetch the next instruction, figure out what it means, do it, repeat. That's the *fetch–decode–execute cycle*, and a CPU runs it billions of times a second. There's no deeper magic — a computer is this loop, spinning forever over a list of tiny instructions.
┌─────────────────────────────────┐
▼ │
FETCH the instruction at address RIP │
│ │
DECODE what does this instruction mean │
│ │
EXECUTE do it (add, move, jump, ...) │
│ │
└────── advance RIP, repeat ───────┘if, loops, and function
calls all work: they change what RIP points at. Control flow is just moving RIP around.2 · Registers: the workbench
The CPU can't compute directly on memory — it's too far away and too slow. Instead it has a handful of tiny, blazing-fast storage slots built right into the chip, called registers. Think of them as the workbench: you pull values from memory (the warehouse) onto the bench, do work, and put results back. Almost every program is this rhythm — load, compute, store.
On x86-64 there are 16 general-purpose 64-bit registers. Some have conventional jobs, but most are yours to use:
| Register(s) | Conventional job |
|---|---|
| rax | results / return values |
| rdi, rsi, rdx, rcx, r8, r9 | the first arguments passed to a function |
| rsp | the stack pointer (top of the stack) |
| rbp | the base/frame pointer |
| rbx, r10–r15 | general scratch space |
| rip | address of the next instruction (not general-purpose) |
| rflags | the 'receipt' — flags set by the last operation |
rax, eax, ax, al are not four registers — they're four *views* of the same one, at 8, 4, 2,
and 1 bytes. Writing al touches only the low byte. This is the 'same bits, different interpretation'
idea from the Numbers subject, now in hardware: the register is 64 switches, and the name you use
decides how many of them you're reading.3 · Memory: one giant array of boxes
Registers are tiny — only 16 of them. Everything else lives in memory (RAM), and the mental model is beautifully simple: memory is one enormous array of numbered boxes, each box holding one byte, each with an address (its box number). Address 0, address 1, address 2, and so on, for billions of boxes.
address: 0x1000 0x1001 0x1002 0x1003 0x1004 ...
┌───────┬───────┬───────┬───────┬───────┐
byte: │ 04 │ 03 │ 02 │ 01 │ ... │
└───────┴───────┴───────┴───────┴───────┘
A 4-byte int lives in 4 consecutive boxes. Its "address"
is the number of its first box.A value bigger than one byte (a 4-byte int, an 8-byte pointer) occupies consecutive boxes, and its address is the number of the first one. The CPU can point at any byte, but not at a single bit — bits only get manipulated once a byte is pulled into a register. This "one big addressable array" is the whole foundation of pointers (a pointer is just one of these addresses) — which you'll meet properly in *C From the Metal*.
0x01020304 sits in
memory as the bytes 04 03 02 01, not 01 02 03 04. The number is the same; the byte *order* in
memory is reversed from how you write it. You'll confirm this yourself in the project by looking at
the actual bytes of an int — a great 'I saw it with my own eyes' moment.0x0A0B0C0D is stored little-endian starting at address 0x1000. Draw the four memory boxes (0x1000–0x1003) and write the byte in each. Then say which byte a char * pointing at 0x1000 would read first.4 · Putting it together
Now the whole picture. The CPU runs its loop: it fetches an instruction from *memory* (at the address in RIP), decodes it, and executes it — which usually means moving data between *registers* and *memory*, or doing arithmetic *in registers* (which sets *flags*), or changing *RIP* to jump. Load from memory to a register, compute on the bench, store the result back. That's it. That's a computer running a program.
The Project
Two parts, both about seeing the machine directly. First, write a C program that reveals your machine's endianness and prints real addresses. Second, use a debugger to watch a running program's registers and memory — making the CPU's workbench and warehouse concrete instead of abstract.
- 01
In
explore.c: declare anint x = 0x01020304;, then print its bytes in memory order by casting its address tounsigned char *and printing all 4 bytes in hex. - 02
From that output, state in a comment whether your machine is little-endian or big-endian, and why (which byte came first).
- 03
Print the addresses (
%p) of three different local variables, and note how they relate (which is higher/lower). - 04
Using gdb on any small compiled program, run
info registersand record the values of rip, rsp, and rax; thenx/8xb &x-style, examine a variable's bytes in memory and confirm they match your C program's output.
- •
Only
<stdio.h>. Read the int's bytes through anunsigned char *, not with shifts (the point is to see memory order). - •
Derive what you EXPECT the byte order to be first (from the endianness section), then check.
- •
Compile with
gcc -O0 -g -Wallso the debugger can see everything.
explore.c (+ your notes from the gdb session)Everything here was taught above: registers and RIP (sections 1–2), memory as addressed bytes and endianness (section 3), and the load/compute/store picture (section 4). Reading an int's bytes through a char pointer is exactly 'a value is bytes at an address.' If a part stumps you, reread its section.