A running C program’s address space is split into segments: code, read-only data, initialised data, zero-initialised data (bss), the heap growing up and the stack growing down.
| Segment | Holds | Lifetime | Notes |
|---|---|---|---|
Text (.text) | Machine code of functions | Program | Read-only + executable; shared between processes |
Read-only data (.rodata) | String literals, const globals | Program | Writing crashes |
Data (.data) | Initialised globals / statics (int g = 5;) | Program | Values stored in the executable file |
BSS (.bss) | Zero / uninitialised globals / statics | Program | Only the size is stored; zero-filled at load |
| Heap | malloc / calloc / realloc | Until free | Grows upward; managed by the allocator |
| Stack | Locals, parameters, return addresses | Until the function returns | Grows downward; ~8 MB default on Linux/macOS main thread |
high addresses: stack ↓ → (free space) → ↑ heap → .bss → .data → .rodata → low addresses: .text
#include <stdio.h>
#include <stdlib.h>
int initialised = 42; /* .data */
int zeroed; /* .bss */
const char *msg = "literal"; /* pointer in .data, the characters in .rodata */
void where(void) { } /* .text */
int main(void) {
int local = 1; /* stack */
static int counter; /* .bss (static, zero) */
int *heap = malloc(sizeof *heap); /* heap */
if (!heap) return 1;
printf("text %p\n", (void *)(size_t)where);
printf("rodata %p\n", (const void *)msg);
printf("data %p\n", (void *)&initialised);
printf("bss %p %p\n", (void *)&zeroed, (void *)&counter);
printf("heap %p\n", (void *)heap);
printf("stack %p\n", (void *)&local);
free(heap);
return 0;
}Inspecting the segments
$ size ./app # text / data / bss sizes (Linux)
text data bss dec hex filename
1845 616 8 2469 9a5 ./app
$ objdump -h ./app # section headers
$ nm ./app | grep ' [bBdD] ' # symbols in bss (b/B) and data (d/D)
$ ulimit -s # stack size limit in KB
8192Stack vs heap
| Stack | Heap | |
|---|---|---|
| Allocation | Automatic (move the stack pointer) | Explicit malloc / free |
| Speed | Very fast | Slower (allocator bookkeeping, possible system call) |
| Size | Small, fixed limit | Large — limited by RAM / address space |
| Lifetime | Ends when the function returns | Until you free it |
| Failure mode | Stack overflow (crash) | malloc returns NULL; leaks, fragmentation |
| Typical bugs | Returning addresses of locals, huge arrays, deep recursion | Leaks, use-after-free, double free |
Stack frames
Each call pushes a frame: return address, saved registers, parameters that don’t fit in registers, and locals. Returning pops it — which is exactly why a pointer to a local is dangling after the return.
#include <stdio.h>
int depth(int n) {
char pad[1024]; /* 1 KB of stack per call */
pad[0] = (char)n;
if (n == 0) return pad[0];
return depth(n - 1) + 1;
}
int main(void) {
printf("%d frames deep, fine\n", depth(100)); /* ~100 KB of stack */
return 0;
}100 frames deep, fineWhere are string literals stored?
What is the difference between .data and .bss?
Why does the stack grow down and the heap grow up?