Computing Systems · a visual field guide

Every program is just bytes with an address.

A running process sees one long, flat array of memory. Where each thing lives in that array — and which direction it grows — explains almost everything about how C behaves. Let's walk the whole address space, top to bottom.

virtual address space · x86-64 · Linux/System V ABI
01 — the big picture

The process address space

This is virtual memory — a private, contiguous map the kernel hands each process. The hardware MMU translates these addresses to scattered physical pages behind the scenes, so every process gets to believe it owns the machine. Tap any region to inspect it.

Stack Heap BSS Data Text / Code

Addresses shown are illustrative. On a real system, ASLR randomises the base of the stack, heap, and libraries on every launch, so absolute values differ each run.

02 — the six neighbourhoods

What lives where, and why

The split isn't arbitrary. The kernel groups memory by two properties it cares about: lifetime (does it exist for the whole program, or come and go?) and permissions (readable? writable? executable?).

Text — the code itself

Your compiled machine instructions live here. It's marked read-only and executable, so a stray pointer can't rewrite your program while it runs, and the same physical pages can be safely shared between multiple processes running the same binary.

Data vs. BSS — the two flavours of global

Both hold variables that outlive any single function: globals and static locals. The difference is whether they start with a value.

Heap — memory you ask for by hand

When you call malloc, calloc, or C++ new, you carve a chunk out of the heap. It grows upward toward higher addresses. Its defining trait: you control the lifetime. It stays alive until you free it — forget to, and you leak; free it twice or use it after freeing, and you get some of the nastiest bugs in systems programming.

Stack — automatic, per-call scratch space

Every function call gets a fresh stack frame holding its parameters, local variables, and bookkeeping. It grows downward. You never manage it directly — the frame appears on entry and vanishes on return, which is exactly why locals are called "automatic" storage. It's fast (just moving one pointer) but small and short-lived. That's the next section.

Why grow toward each other? The heap climbs up from below and the stack descends from above, with the huge unused gap between them shared. Neither has to commit to a fixed size in advance — whichever needs more room grows into the middle. They only collide in extreme cases (a stack overflow, or exhausting all memory).
03 — the subtopic in focus

How C functions live in memory

A function call is a small, precise ritual on the stack. Watch it happen: step through a real call chain and see frames get pushed on entry and popped on return. The highlighted line is what's executing.

— higher addresses (frame base) —
↓ stack grows down as calls nest ↓
0

The anatomy of one frame

When you look at a single frame, three kinds of things sit inside it, and the order is set by the calling convention (the System V ABI on x86-64 Linux):

argument #register (integer/pointer)where it goes
1strdiregister
2ndrsiregister
3rdrdxregister
4th – 6thrcx, r8, r9register
7th onwardpushed onto the stack
Why the stack is so fast: allocating a whole frame's worth of locals is a single subtraction on the stack pointer (sub rsp, N). Freeing them all on return is a single addition. No searching, no free lists, no fragmentation — the price is that everything vanishes the instant the function returns, which is why returning a pointer to a local is a bug.
04 — layout & alignment

How a struct sits in memory

A struct is not just its fields added up. The compiler inserts invisible padding so each field lands on an address that's a multiple of its size — the CPU reads aligned data faster (and on some architectures, misaligned reads simply fault). Pick a struct and watch the bytes fall into place.

field a field b field c field d padding
Apply #pragma pack(1) — remove all padding

Packing removes padding to save space, but every misaligned field now costs extra CPU work to read — a real tradeoff, not a free win.

The reorder trick: the same three fields, sorted largest-to-smallest, often shrink the struct. Compare Mixed and Reordered above — identical data, different size, purely because of where the padding has to go. This is why field order matters in hot data structures.
05 — byte order

Endianness

A single byte is unambiguous. But a multi-byte value like a 4-byte int has to be split across four consecutive addresses — and there are two conventions for which byte goes first. That choice is endianness. Type a value and watch it land in memory both ways.

Little-endian least-significant byte at the lowest address · x86, ARM (default), RISC-V
low address high address
Big-endian most-significant byte first · network byte order, older SPARC / PowerPC
low address high address
Why you actually meet this: write an int to a file or socket on a little-endian machine, read it on a big-endian one, and the value is scrambled. That's why network protocols mandate big-endian ("network byte order") and you call htonl() / ntohl() to convert. The gold-underlined cell marks the most-significant byte in each layout.