How does the heap works in microcontrollers?

On a microcontroller, the heap is just a region of RAM used for dynamic allocation (e.g., malloc/new) at runtime. The big difference vs a PC is: RAM is tiny, there’s no OS to manage memory for you, and fragmentation can bite hard.
1) The memory map idea (Flash vs RAM)
Typical MCU layout:
Flash: your program code + constant data
RAM: variables + stack + heap
Inside RAM you usually have (conceptually):
Low RAM address High RAM address
| .data/.bss | HEAP ---> <--- STACK | (top of RAM)
.data/.bss: global/static variablesHeap grows upward (toward higher addresses)
Stack grows downward (from top of RAM downward)
If heap and stack collide → crash/hard fault.
(Some linkers can place them differently, but this is the common model.)
2) What actually happens when you call malloc()
A) The allocator keeps bookkeeping
malloc() is implemented by a memory allocator (often newlib, dlmalloc variant, or a vendor lib). It manages free/used blocks in the heap, usually with:
block headers (size, flags)
free lists / bins
splitting blocks
coalescing neighboring free blocks
B) The allocator needs “more heap”
When the allocator runs out of free heap space, it asks the system for more memory. On bare-metal MCUs, this is typically done via a function like _sbrk() (newlib). Your _sbrk() moves a pointer (the “program break”) up inside the heap region.
So the heap is often just:
a start address
a current end pointer
an upper limit (before it hits stack or a configured boundary)
3) Why heap can be dangerous on MCUs
Fragmentation
If you allocate/free different sizes over time, the heap can become fragmented:
total free RAM might be “enough”
but not as one contiguous block
so
malloc()fails even though RAM looks available.
Non-deterministic timing
malloc/free time can vary based on heap state (searching free lists, coalescing), which is bad for real-time code.
Concurrency/interrupt issues
If you call malloc() from an ISR while the main code also allocates, you can corrupt the heap unless:
the allocator is reentrant, and/or
you protect it with critical sections (often not desired in ISRs).
Stack/heap collision
Deep call stacks, big local arrays, or recursion can make the stack grow down into the heap.
4) How to use heap safely (best practices)
Prefer static or fixed-size pools in real-time code
Use static buffers or object pools
Use a fixed-block allocator (same-sized blocks = no fragmentation)
Use an RTOS heap scheme (e.g., FreeRTOS heap_4/heap_5) if applicable.
If you do use malloc/new:
Allocate at startup, then don’t free (“allocate once” pattern)
Avoid allocating in interrupts
Keep allocations same-sized where possible
Check return values (
NULL) and handle out-of-memory gracefully
Watch your stack
Avoid huge local arrays; put big buffers in
.bss(global/static) or a dedicated poolEnable stack overflow checking if your environment supports it
5) Common MCU patterns you’ll see
No heap at all: project disables
malloc/newTiny heap: only for libc internals (e.g.,
printf,newlibreentrancy)Custom heap: simple bump allocator, or TLSF for deterministic-ish behavior
RTOS heaps: managed separately from the C heap
6) Quick way to “see” it on STM32 (conceptually)
In many STM32 toolchains:
heap size is set in the linker script (or in IDE settings)
_sbrk()is provided by syscalls (or you implement it)stack size also configured
If heap size = 0,malloc()will always fail.




