Sitemap
Pavol Z. Kutaj

Pavol Z. Kutaj

Support Engineer at ClickHouse. On programming, most of the time.

Explaining The Heap

3 min readMay 26, 2026

--

The aim of this pageπŸ“ is to explain the heap β€” what it is, how it differs from the stack, and why C programmers must manage it explicitly.

1. Two runtime memory regions

  • stack and heap are both runtime regions inside a process’s virtual address space
  • the distinction only exists at runtime β€” compile time decides strategy, runtime populates/depopulates
  • virtual address space layout (simplified):

2. Thread:Stack is 1:1 β€” Thread:Heap is many:1

  • every thread owns exactly one call stack
  • all threads share one heap
  • stack = private β†’ thread-safe by default
  • heap = shared β†’ needs synchronization under concurrent access
  • think of threads as equalizer bars: each bar (thread) has its own height (stack depth)
  • the heap is a different dimension β€” outside the equalizer, persistent, reachable from any bar

https://gist.github.com/pkutaj/f038f4bfcd1a51d1cbe38fe6a39dcc05

  • stack frames are temporary β€” exist only for the duration of a call
  • heap objects are long-lived β€” survive across frames, reachable via pointers

3. Stack: fast, automatic, scoped

  • in C, function arguments are copied onto the stack frame (pass-by-value)
  • local variables also live in the frame
  • once the function returns, the frame is gone β€” memory is reclaimed automatically
  • the CPU has native instructions for stack push/pop β†’ very fast

Example function β€” all data fits on the stack:

Stack frame for this call:

C type sizes (platform-dependent):

Get Pavol Z. Kutaj’s stories inΒ yourΒ inbox

Join Medium for free to get updates fromΒ thisΒ writer.

https://gist.github.com/pkutaj/3028f854fbed57c804939c2a4a06c6dd

4. Heap: slow, manual, process-wide

  • heap memory is not automatically freed β€” you must explicitly release it
  • forgetting to free = memory leak β†’ process slowly consumes all available RAM
  • heap is slower: the allocator must find a free block, track metadata, handle fragmentation

The three reasons to reach for the heap:

  1. Unknown size β€” size is not known at compile time; must allocate dynamically at runtime
  2. Cross-scope / long-lived β€” data must outlive the function that created it
  3. Too large β€” stack is typically 1–8 MB; large buffers must go on the heap

Rule of thumb:

  • stack when size is known ahead of time and data lives within one function
  • heap when size is unknown or data must survive across function boundaries

5. malloc and free β€” the C heap API

  • malloc(size_t n) β€” asks the OS for n bytes on the heap; returns void *
  • cast to your target type: (int *)
  • sizeof(int) is idiomatic β€” let the compiler determine size, don't hardcode 4
  • free(ptr) β€” returns memory to the OS; omitting it = leak

6. Aside: you can’t shovel the heap onto the wire

  • if the call stack is a stack data structure (of frames), the heap is a graph (of pointers)
  • pointers are virtual addresses β€” process-local, ephemeral
  • 0xFF1234 is meaningful only inside this running process
  • different run, different machine β†’ that address is garbage
  • heap also contains allocator metadata, padding, alignment gaps
  • ASLR (Address Space Layout Randomization) randomizes base addresses on every run
  • serialization = traverse the pointer graph, emit only values
  • deserialization = rebuild the pointer graph on the other end
  • this is why every network protocol, file format, and IPC mechanism involves serialization

Why columnar formats (e.g. ClickHouse) are fast on the wire

  • columns are already close to wire-ready: contiguous values, same type
  • minimal pointer indirection to resolve before sending
  • contrast with row-oriented formats where each row may contain pointers to variable-length strings

References

--

--

Pavol Z. Kutaj
Pavol Z. Kutaj

Written by Pavol Z. Kutaj

Support Engineer at ClickHouse. On programming, most of the time.