How Programs Really Run: From CPU to Process Memory
Type this right now
// save as where.c, compile: gcc -g -o where where.c
#include <stdio.h>
#include <stdlib.h>
int global = 99;
int main() {
int stack_var = 42;
int *heap_var = malloc(sizeof(int));
*heap_var = 7;
printf("Code: %p\n", (void *)main);
printf("Global: %p\n", (void *)&global);
printf("Stack: %p\n", (void *)&stack_var);
printf("Heap: %p\n", (void *)heap_var);
free(heap_var);
return 0;
}
Run it. You'll see four wildly different addresses. Those addresses tell a story — a story about how your operating system, your CPU, and your compiler conspire to make your program work.
By the end of this book, you'll know exactly why each of those addresses is where it is.
What this book is about
When you write int x = 42; in C or let x: i32 = 42; in Rust, a staggering amount of
machinery activates. The compiler translates your intent into machine instructions. The linker
stitches object files into a binary. The OS loader maps that binary into virtual memory. The CPU
fetches instructions one by one, reading and writing to registers and RAM.
Most programmers treat all of that as a black box. This book opens the box.
We won't hand-wave. We won't say "the OS handles it" and move on. We'll show you the actual mechanism — the CPU instruction, the kernel data structure, the page table entry — and then give you a tool to observe it yourself on a real, running system.
Who this is for
You should already know how to write code in C or Rust (or both). You don't need to be an expert. If you can write a function, allocate memory, and compile a program, you have enough background.
What you probably don't know yet:
- Why your stack variable lives at
0x7ffd...but your heap variable lives at0x55a... - What the CPU actually does with
mov rax, [rbp-8] - Why a segfault happens at the hardware level
- What
straceis showing you and why it matters - How Rust's borrow checker maps to the same physical reality as C's raw pointers
That's what we're here for.
C and Rust, side by side
Every concept in this book is demonstrated in both C and Rust. Not because one is better — because seeing the same low-level reality from two different languages makes both clearer.
C gives you no guardrails. You see the raw mechanism. Rust adds compile-time guarantees. You see how safety is enforced without runtime cost.
Same CPU. Same instructions. Same virtual memory. Different contracts with the programmer.
How to read this book
Every chapter follows the same structure:
- Something you can type and run in 30 seconds. Seeing is believing.
- The concept, explained with ASCII diagrams. No hand-waving.
- The mechanism. What the CPU/OS/compiler actually does.
- Code in C and Rust. Side by side.
- A tool to observe it live. GDB, strace, /proc, objdump, readelf.
- A hands-on task. You learn by doing, not by reading.
You can read front-to-back, or jump to any chapter that interests you. But Part I (The Machine) is worth reading first — everything else builds on it.
What you'll need
- A Linux system (native, WSL2, or a VM all work)
- GCC and Rust (rustc/cargo) installed
- GDB, strace, objdump, readelf (standard on most Linux distros)
- A text editor and a terminal
That's it. No special hardware. No expensive tools. Everything we use is free and open source.
The journey
Part I: The Machine — CPU, memory, instructions, privilege
Part II: The Illusion — How your program sees memory
Part III: The Binary — ELF files, compilation, linking, loading
Part IV: The Mechanism — Virtual memory, page tables, page faults
Part V: Allocation — malloc, Rust's allocator, data layout
Part VI: Threads and Safety — Shared memory, C vs Rust tradeoffs
Part VII: Observe and Build — Tools and experiments
By the end, int x = 42 won't be magic anymore. You'll know the register it passes through,
the cache line it occupies, the page table entry that maps it, and the virtual address the OS
assigned to it.
Let's start with the CPU.