Worked examples — Memory layout — text, data, BSS, heap, stack segments
This page is a drill: we hunt down every kind of question the memory-layout topic can throw at you and solve one of each — completely. If you have not yet met the segments, read the parent first: Memory layout — text, data, BSS, heap, stack segments.
The scenario matrix
Read each row as a case class — a distinct kind of situation. The rightmost column names the example that nails it.
| # | Case class | Concrete trigger | Correct segment | Example |
|---|---|---|---|---|
| 1 | Global, non-zero init | int g = 5; |
Data | Ex 1 |
| 2 | Global, zero / no init | int g; or int g = 0; |
BSS | Ex 1 |
| 3 | static inside a function |
static int s = 0; |
BSS/Data | Ex 1 |
| 4 | Local (auto) variable | int x; in a function |
Stack | Ex 1, Ex 3 |
| 5 | Pointer vs its target | int *p = malloc(...) |
p→Stack, *p→Heap |
Ex 2 |
| 6 | String literal | char *s = "hi"; |
Text (RO) | Ex 4 |
| 7 | Local array on the stack | char buf[10]; |
Stack | Ex 4 |
| 8 | Code / a function itself | void f(void){...} |
Text (RO) | Ex 4b |
| 9 | Disk-size degenerate (Data↔BSS) | huge array, zero vs non-zero | Data grows file / BSS doesn't | Ex 5 |
| 10 | Limiting case — collision | deep recursion / giant malloc | Stack overflow / NULL |
Ex 6 |
| 11 | Word problem (real world) | server session buffers | Heap sizing | Ex 7 |
| 12 | Exam twist — freed memory | use pointer after free |
dangling (Heap) | Ex 8 |
| 13 | Growth direction proof | compare addresses numerically | Stack↓, Heap↑ | Ex 3 |
We now walk every row.
Ex 1 — Rows 1–4: classify every declaration
The decision procedure — apply to each name. Ask three yes/no questions in order:
- Is it a function's local without
static? → Stack. (Its lifetime is the function call.) - Otherwise it has static storage. Is its initial value non-zero? → Data (the value must be stored on disk).
- Static storage, value zero or absent? → BSS (only a "reserve & zero" note is stored).
Now grind through:
| Var | Q1 local-auto? | Q2 non-zero? | Segment | Why this step? |
|---|---|---|---|---|
a =5 |
no (global) | yes | Data | value 5 must survive on disk |
b =0 |
no | no | BSS | zero carries no info → no disk bytes |
| c (none) | no | no (defaults to 0) | BSS | uninit global = guaranteed 0 |
d static=7 |
no (static) | yes | Data | static storage + non-zero |
e =3 |
yes | — | Stack | plain local → dies when run returns |
f static=0 |
no (static) |
no | BSS | static beats "inside function": static storage, zero |
g static=9 |
no (static) |
yes | Data | static storage, non-zero |
Verify (Why it's consistent): exactly the two non-zero static things (a, d, g) hit Data; the two zero static things (b, f) plus the uninitialized global (c) hit BSS; only the plain local e hits Stack. Count: Data = 3, BSS = 3, Stack = 1 — matches the matrix rows 1–4.
Ex 2 — Row 5: the pointer vs the block it points to
- Separate the two objects. Why this step?
mallocproduces two distinct things: (i) the variablep, a pointer, and (ii) the 40-byte block it will point at. Never merge them. - Locate
p.pis a plain local ofdemowith nostatic→ question 1 = yes → Stack. Why: it lives only whiledemoruns. - Locate the block.
mallocasks the heap allocator for 40 bytes → Heap. Why: the heap holds runtime requests that must outlive the function if you return the pointer.

Verify (sanity check): if you return p; from demo, the returned address still points at valid heap memory (the block survives), but you could never return &(a local) safely — that address dies. This asymmetry is the whole reason heap exists.
Ex 3 — Rows 4 & 13: prove the growth directions with real numbers
- Compare the two stack addresses.
&x = ...a1c8,&y = ...a1c4. The later-declaredysits at the lower address (). Why this step? consecutive locals are laid out as the frame is built, so their order reveals direction. - Read the difference. bytes — exactly one
int. New locals go down by their size → stack grows toward lower addresses. ↓ - Compare the two heap addresses.
q = ...880010,p = ...880000. The secondmallocsat higher (). Why: the program break moves up as you request more. - Read the difference. bytes. The heap handed out 16 bytes for a request the allocator rounded up. New blocks go up → heap grows toward higher addresses. ↑

Verify (units & magnitude): stack step = 4 bytes = sizeof(int) ✓. Heap addresses () are astronomically lower than stack addresses () — confirming heap-below, stack-above, exactly as the layout formula predicts. Difference of the two malloc addresses = .
Ex 4 — Rows 6 & 7: string literal vs local buffer (the classic confusion)
- Case (1): the pointer and the literal.
litis a local pointer → Stack. The string literal"hello"it points to is a compile-time constant baked into the read-only Text area. Why: literals never change, so the compiler stores one shared, read-only copy — writing throughlitis undefined behaviour. - Case (2): the array.
bufis a local array of 6 chars. All 6 bytes live insidegreet's stack frame; the initializer"hello"is copied into them at runtime. Why: an array is storage, not a pointer — so its bytes sit where the variable sits (Stack), and you can modifybuf[0].
Verify: sizeof(lit) = 8 (a pointer on 64-bit); sizeof(buf) = 6 (six chars). Different sizes ⇒ different objects ⇒ different homes. ✓
Ex 4b — Row 8: where does the code itself live?
- Locate the instructions of
greet. Why this step? a function is not a variable; it is a block of machine instructions produced by the compiler. Those instructions must sit somewhere executable and unchanging → the Text segment (read-only, shared). Why Text and not Data: code never mutates while running, so making it read-only stops both bugs and viruses from rewriting it. - Locate
fp.fpis a plain local ofmain→ question 1 = yes → Stack. It merely holds the address ofgreet, exactly like Ex 2'spheld a heap address. Why: the pointer and the thing it points at live in different segments. - Predict the printed address. Because
greetlives in Text (low addresses, near the bottom of the layout),fpprints a value far below stack addresses like0x7ffe...— usually in the0x...55...range on Linux.
Verify: sizeof(fp) = 8 (function pointer on 64-bit). Calling fp() runs greet — proof the address is a valid, executable Text address. The address is much smaller than any 0x7ffe... stack address. ✓
Ex 5 — Row 9: the disk-size degenerate case (Data ↔ BSS swap)
- Version B (all zero) → BSS. Why: every element is zero, so the file stores only "reserve bytes, zero them." Disk cost ≈ a few bytes of bookkeeping, essentially payload.
- Version A (one non-zero) → Data. Why: the array is initialized, so the compiler must be able to reproduce all integers — even though of them are zero, the array's declared value is no longer "all zero," so the whole block goes to Data and is written out.
- Compute the Data payload. Why this step? the file must physically hold every one of the integers, and each
intoccupiessizeof(int)= 4 bytes — so total bytes = (number of elements) × (bytes per element); multiplying count by element size is just "how many boxes × size of each box."
Verify (runtime vs disk): at runtime both versions occupy the same of RAM (BSS is zeroed into memory too). The saving is purely on disk: version B's file is ~4 MB smaller. Units check: bytes × count = bytes ✓. (.)
Ex 6 — Row 10: the limiting case (collision → overflow)
- (a) Stack budget ÷ frame size. Why this step? the stack pointer sinks by exactly one frame per call, so the number of calls that fit is "total room ÷ room-per-call" — the same reason you divide a shelf's length by one book's thickness to count how many books fit. bytes. Divide by frame size :
So around the 4096th deep call the stack pointer crosses its guard page → stack overflow (a segment fault, not a
mallocfailure). - (b) Heap budget ÷ block size. Why this step? each successful
malloc(1<<20)consumes one fixed 1 MiB chunk of free memory, so the count that fits before exhaustion is again "total free ÷ per-chunk" — identical logic to (a), just a different budget and chunk. Each block = bytes. With bytes available: Around block 8192,mallocreturnsNULL— a different failure mode (heap exhaustion), which you can test and recover from.
Verify: ✓. ✓. Both divisions are exact.
Ex 7 — Row 11: real-world word problem
- Real bytes per user. Why this step? the heap must store your data plus the allocator's own metadata, so the true footprint of one user is data + overhead, not just the 512 you asked for. Per block: bytes.
- Multiply by users. Why this step? every user is an independent heap block of the same size, so total heap = (bytes per block) × (number of blocks) — the same "count × size-of-each" reasoning as Ex 5. bytes.
- Convert to MiB. Why this step? the question asks in MiB, and bytes by definition, so we divide the raw byte total by to change units. .
Verify (which segment & sanity): these records live on the Heap (runtime malloc), not Data/BSS — their count is unknown at compile time. The pointers indexing them (an array of 20,000 pointers) would themselves be bytes elsewhere. Peak heap ✓ (the 16-byte overhead pushed us just over).
Ex 8 — Row 12: the exam twist (use-after-free)
- Where is
p?pis a plain local pointer ofmake→ question 1 = yes → Stack, both before and after thefree. Why this step:freeacts on the heap block, never on the stack slot that stores the address — separating the two objects (as in Ex 2) is exactly what tells usp's home is untouched. - What did
freechange? It returned the 4-byte heap block to the allocator. The address value sitting insidepis unchanged, but it now points at memory you no longer own →pis a dangling pointer. Why this matters: the allocator is free to hand those same bytes to the nextmalloc, so the block's contents are no longer yours to trust. - Is
*psafe to read? No. Why: reading through a dangling pointer is undefined behaviour — the block may hold42, garbage, or another object's data, and even the read itself may fault. Nothing is guaranteed. - The fix. Immediately do
p = NULL;afterfree, and neverreturn pfor a freed block (returning it invites the caller to dereference dead memory). See Pointers and dangling pointers.
Verify: the value *p was 42 before free; after free its value is undefined (not guaranteed 42, not guaranteed anything). The only guaranteed fact is: p remained on the Stack throughout, and the freed block itself was on the Heap. ✓
Active recall
Recall Which cell does each land in?
static int k = 4;inside a function ::: Data (static storage, non-zero)char *s = "abc";— where is"abc"? ::: Text (read-only literal)char buf[4] = "abc";— where isbuf? ::: Stack (local array)- The compiled body of a function
f::: Text (read-only code) int *p = malloc(8)— where isp, where is the block? ::: p on Stack, block on Heap- Reading
*pafterfree(p)::: undefined behaviour; p is a dangling Stack pointer int big[1000000];vs= {1}— what differs? ::: on-disk file size (BSS vs Data), not runtime RAM
Connections
- malloc, calloc, realloc and free — the engine behind Ex 2, 6, 7.
- Stack frames and the calling convention — why Ex 3's locals sink downward.
- Static and global variables — storage duration — the rule powering Ex 1.
- Pointers and dangling pointers — Ex 8's use-after-free.
- Buffer overflow and stack smashing — Ex 6's stack-limit collision.
- Virtual memory and paging — why the middle gap between heap and stack exists at all.