Memory Layout — Stack vs Heap vs Data Segments
A running C process has its virtual address space divided into distinct segments. Understanding this layout is the mental model behind every allocation decision — why local variables disappear when a function returns, why static/global variables persist, and why `malloc` and `free` must be balanced manually.
Process Memory Map — All Five Segments
/* Virtual address space of a C process (simplified, 64-bit Linux)
High addresses (typically ~0x7fff_ffff_ffff on x86-64)
┌────────────────────────────────────────┐
│ STACK │ ← grows downward
│ - Local variables │
│ - Function arguments (copies) │
│ - Return address, saved frame ptr │
│ - Automatically managed (push/pop) │
│ - Typically 1–8 MB limit │
├────────────────────────────────────────┤
│ (unmapped — stack grows into this) │
├────────────────────────────────────────┤
│ HEAP │ ← grows upward
│ - malloc(), calloc(), realloc() │
│ - Manually managed: you call free() │
│ - Limited only by available RAM + swap│
├────────────────────────────────────────┤
│ BSS (Block Started by Symbol) │
│ - Uninitialised global/static vars │
│ - Zero-filled by OS at startup │
│ - int zero_global; ← here │
├────────────────────────────────────────┤
│ DATA (initialized data segment) │
│ - Initialised global/static vars │
│ - int counter = 10; ← here │
├────────────────────────────────────────┤
│ TEXT (code segment) │
│ - Compiled machine code │
│ - Read-only: string literals live here│
│ - const char *s = "hello" ← "hello" │
└────────────────────────────────────────┘
Low addresses (~0x0000_0000_4000_00 for text) */
#include <stdio.h>
#include <stdlib.h>
int global_init = 42; /* DATA segment */
int global_uninit; /* BSS segment */
int main(void) {
int stack_var = 10; /* STACK */
static int static_var; /* BSS — static local, zero-initialized */
int *heap_ptr = malloc(sizeof(int)); /* HEAP */
if (heap_ptr) { *heap_ptr = 99; free(heap_ptr); }
printf("TEXT (function): %p
", (void *)main);
printf("DATA (global): %p
", (void *)&global_init);
printf("BSS (uninit): %p
", (void *)&global_uninit);
printf("STACK (local): %p
", (void *)&stack_var);
/* Note the address ordering: TEXT low, STACK high */
return 0;
}Common Mistakes
- Thinking the heap is 'slower' — heap allocation has overhead (the allocator searches for free blocks), but reading/writing heap memory is not slower than stack memory once allocated.
- Confusing BSS with uninitialised memory — BSS variables ARE zero-initialised by the OS at program start. But this is a C language guarantee, not something to exploit for clarity. Always initialise explicitly.
- Stack overflow from deep recursion or large local arrays — `void f(void) { int buf[1000000]; }` allocates 4MB on the stack (often exceeds the 1–8 MB limit). Large buffers must go on the heap via `malloc`.
Tip
Tip
Practice Memory Layout Stack vs Heap vs Data Segments in small, isolated examples before integrating into larger projects. Breaking concepts into small experiments builds genuine understanding faster than reading alone.
Understanding memory layout is essential for efficient C programming
Practice Task
Note
Practice Task — (1) Write a working example of Memory Layout Stack vs Heap vs Data Segments from scratch without looking at notes. (2) Modify it to handle an edge case (empty input, null value, or error state). (3) Share your solution in the Priygop community for feedback.
Quick Quiz
Common Mistake
Warning
A common mistake with Memory Layout Stack vs Heap vs Data Segments is skipping edge case testing — empty inputs, null values, and unexpected data types. Always validate boundary conditions to write robust, production-ready c code.
Key Takeaways
- A running C process has its virtual address space divided into distinct segments.
- Thinking the heap is 'slower' — heap allocation has overhead (the allocator searches for free blocks), but reading/writing heap memory is not slower than stack memory once allocated.
- Confusing BSS with uninitialised memory — BSS variables ARE zero-initialised by the OS at program start. But this is a C language guarantee, not something to exploit for clarity. Always initialise explicitly.
- Stack overflow from deep recursion or large local arrays — `void f(void) { int buf[1000000]; }` allocates 4MB on the stack (often exceeds the 1–8 MB limit). Large buffers must go on the heap via `malloc`.