pointers-memory Lesson 6 22 min read

How Is Memory Organized — Stack, Heap, and Beyond?

Text, data, BSS, stack, heap — where your variables actually live and why it matters

Reading: C Text: Ch. 13 §2 (pp. 740–750, Dynamic Memory), Ch. 9 §1 (pp. 543–555, Recursion/Stack)

After this lesson, you will be able to:

  • Identify the five segments of a C program’s memory layout (text, data, BSS, heap, stack)
  • Determine which memory segment a given variable resides in
  • Explain how the stack grows and shrinks with function calls
  • Compare stack and heap memory in terms of lifetime, size, and risks

Where Does Your Data Actually Live?

When you write int x = 42;, where does x exist? “In memory” is technically correct but not helpful. Your program’s memory is divided into distinct regions, each with different properties. Understanding which region holds what — and what happens when each region runs out — is the foundation for dynamic memory allocation.


The Memory Map

Program Memory Layout

When your compiled C program runs, the OS divides its memory into segments:

High addresses
┌──────────────────┐
│      Stack       │ ← Local variables, function calls
│        ↓         │   Grows DOWN
│                  │
│    (free space)  │
│                  │
│        ↑         │
│       Heap       │ ← Dynamic allocation (calloc/malloc)
│                  │   Grows UP
├──────────────────┤
│       BSS        │ ← Uninitialized globals (zeroed)
├──────────────────┤
│       Data       │ ← Initialized globals/statics
├──────────────────┤
│       Text       │ ← Your compiled code (read-only)
Low addresses
└──────────────────┘

The Stack

The stack holds:

  • Local variables
  • Function parameters
  • Return addresses
void function_b(int y)
{
    int local_b = y * 2;    // On the stack
}

void function_a(void)
{
    int local_a = 5;         // On the stack
    function_b(local_a);
}

int main(void)
{
    int x = 42;              // On the stack
    function_a();
    return 0;
}

Each function call creates a stack frame — a block of memory containing its parameters and local variables. When the function returns, its frame is destroyed:

During function_b:          After function_b returns:
┌──────────────┐           ┌──────────────┐
│ function_b   │           │              │
│  y = 5       │           │ (reclaimed)  │
│  local_b = 10│           │              │
├──────────────┤           ├──────────────┤
│ function_a   │           │ function_a   │
│  local_a = 5 │           │  local_a = 5 │
├──────────────┤           ├──────────────┤
│ main         │           │ main         │
│  x = 42      │           │  x = 42      │
└──────────────┘           └──────────────┘

Key Insight: This is why you can’t return a pointer to a local variable — the stack frame is destroyed when the function returns. The pointer would point to reclaimed memory. Dynamic allocation (heap memory) solves this: heap memory persists until you explicitly free it.

Check Your Understanding
What causes a stack overflow?
A Calling free() too many times
B Allocating too much memory with calloc
C Too many nested function calls (e.g., infinite recursion) exhausting the stack
D Declaring too many global variables
Answer: C. Each function call adds a stack frame. The stack has a fixed size (typically 1-8 MB). Infinite recursion or very deep call chains exhaust it. Option A causes a double-free (undefined behavior, not stack overflow). Option B affects the heap, not the stack. Option D uses the data/BSS segments, not the stack.

The Heap

The heap holds dynamically allocated memory — memory you request at runtime with calloc/malloc and release with free:

int *arr = calloc(100, sizeof(int));    // 400 bytes on the heap
// ... use arr ...
free(arr);                               // Release back to the heap

Unlike stack memory, heap memory:

  • Persists until you explicitly free it (or the program exits)
  • Can be returned from functions safely
  • Must be manually managed (no garbage collector)

The Data and BSS Segments

int global_init = 42;        // Data segment (initialized)
int global_uninit;           // BSS segment (zeroed by OS)
static int static_var = 10;  // Data segment (initialized)

These live for the entire program lifetime.

The Text Segment

Your compiled machine code lives here. It’s read-only — the OS prevents you from modifying it (which would corrupt your program).

Stack vs. Heap Summary

Property Stack Heap
Allocation Automatic (function calls) Manual (calloc/free)
Deallocation Automatic (function returns) Manual (free)
Size Limited (typically 1–8 MB) Large (limited by OS)
Speed Fast (just move stack pointer) Slower (memory management)
Lifetime Until function returns Until you free it
Risk Stack overflow (deep recursion) Memory leaks (forgot free)

From Java: In Java, all objects live on the heap, and the garbage collector frees them automatically. Local primitives live on the stack. C gives you the same two regions — but you manually choose where data goes and when it’s released. The stack is automatic; the heap is manual.

Check Your Understanding
Where does each variable live?

int global = 10;
void foo(void) {
    int local = 20;
    int *heap_ptr = calloc(1, sizeof(int));
}
A global: stack, local: stack, *heap_ptr: heap
B global: data segment, local: stack, heap_ptr: stack, *heap_ptr: heap
C global: data segment, local: heap, heap_ptr: heap
D global: BSS, local: stack, heap_ptr: heap
Answer: B. global is an initialized global — it goes in the data segment. local is a local variable — stack. heap_ptr itself is a local pointer variable — also stack. But the memory heap_ptr points to (allocated by calloc) is on the heap. Option A puts the global on the stack. Option D says BSS, but global is initialized (BSS is for uninitialized globals).
Why does this matter?

Knowing which memory segment holds your data tells you its lifetime, its size limits, and what happens when things go wrong. Stack variables vanish on return. Heap memory leaks if you forget free. Globals persist forever. Every debugging session gets easier when you can mentally place each variable on the memory map.

Quick Check: Why can't you return a pointer to a local variable?

Local variables live on the stack. When the function returns, its stack frame is destroyed and the memory is reclaimed. A pointer to that location now points to freed memory — using it is undefined behavior.

Quick Check: What's the difference between stack and heap allocation?

Stack allocation is automatic — local variables are created when the function is called and destroyed when it returns. Heap allocation is manual — you call calloc to allocate and free to release. Stack is fast but limited in size; heap is larger but requires manual management.

Quick Check: Where does a global variable live?

In the Data segment (if initialized, e.g., int x = 5;) or BSS segment (if uninitialized, e.g., int x;). Both persist for the entire lifetime of the program.


You Now See the Machine

Understanding the memory layout means you can predict where variables live, why stack overflow happens (too many recursive calls), and why memory leaks occur (forgot to free heap memory). This mental model is the foundation for the next four lessons.

Next: dynamic memory allocation with calloc, malloc, and free. You’ll finally be able to create arrays whose size is determined at runtime — something Java’s new does behind the scenes.

Big Picture: The memory map is the mental model that ties everything together. When you debug a segfault, you’re asking “which segment did this pointer land in?” When you choose between a local array and calloc, you’re choosing between stack and heap. When you wonder why a server process uses 2 GB of RAM, you’re looking at heap growth. This diagram is the map you’ll reference for the rest of the course.