Lesson 1 of 9 · 20m
How C Lays Out Memory: Stack, Heap, and Addresses
Map a real process: text, data, bss, heap, stack, and how to read /proc/<pid>/maps and ASLR's effect.
You inherit a daemon that crashes once a week under load, and the core dump is full of hex addresses nobody explains. Before you can chase a memory bug — or write C that doesn't ship one — you need a working model of where things actually live in a process. C gives you almost no abstraction here, which is exactly why the model matters.
A Linux process's address space is a handful of regions, each with its own lifetime and permissions:
.text— your compiled machine code, read-only and executable..rodata— string literals andconstdata..data/.bss— globals andstaticlocals;.bssis the zero-initialized part.- The heap — what
mallochands you, growing upward viabrk/mmap. - Memory-mapped regions — shared libraries, and any
mallocchunk big enough that glibc served it withmmapinstead ofbrk. - The stack — one per thread, holding each call's frame: locals, saved registers, and the return address. It typically grows downward on x86-64.
You can see all of it without a debugger:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
static int counter; /* .bss — zeroed */
const char banner[] = "sudo>"; /* .rodata */
int verbose = 1; /* .data */
int main(void) {
int local = 42; /* stack */
void *heap = malloc(64); /* heap */
printf("main %p\n", (void *)main);
printf("banner %p\n", (void *)banner);
printf("verbose %p\n", (void *)&verbose);
printf("counter %p\n", (void *)&counter);
printf("heap %p\n", heap);
printf("local %p\n", (void *)&local);
printf("pid %d — check /proc/%d/maps\n", getpid(), getpid());
sleep(60);
free(heap);
return 0;
}gcc -O0 -g layout.c -o layout
./layout &
cat /proc/$(pgrep layout)/mapsMatch each printed address to a region in maps. The heap pointer lands inside [heap] for small allocations; local lands in [stack]; banner sits in a read-only page of the executable's own mapping.
Try this
Run ./layout twice and diff the addresses. They move — that's ASLR (address space layout randomization), the mitigation that makes exploit addresses unstable. Run it once with setarch -R ./layout to disable ASLR and watch the addresses stabilize. Then bump the malloc(64) to malloc(1 << 24) and look at maps again: the allocation moved out of [heap] into an anonymous mmap region.
Field notes
- "The stack grows down" is an architecture convention, not a C guarantee — don't write code that depends on direction, only on lifetime.
- Print pointers with
%pand cast tovoid *. Using%xon a pointer is a classic warning-silencer that truncates on 64-bit. - The two stack traces that matter later — where memory was allocated and where it was touched — both become readable once you can name these regions. Keep this lesson's program; you'll rebuild it under ASan.