Heap fragmentation is the condition in which available free memory on the heap is divided into many small, non-contiguous blocks, so that the total free space is large enough to satisfy an allocation request but no single contiguous region is. It is sometimes informally called memory fragmentation, though that broader term can also refer to other kinds of fragmentation such as internal fragmentation or physical page fragmentation.
In practice
In embedded systems, heap fragmentation typically develops over time as the firmware repeatedly allocates and frees blocks of varying sizes. Over many allocation cycles, gaps accumulate in the free-space layout, and when the next allocation requests a size larger than any individual gap, the allocator fails even if aggregate free memory is ample. This is especially dangerous in long-running systems such as data loggers, gateways, or industrial controllers where the heap degrades gradually and a crash may not appear until the device has been running for days or weeks.
Most small embedded allocators (newlib's malloc, dlmalloc, and similar) use some form of free-list coalescing to merge adjacent free blocks, which reduces but does not eliminate fragmentation, though specific builds or stripped-down embedded implementations may not coalesce aggressively. Allocators that lack coalescing, or that run on highly constrained devices (8-bit AVR, PIC, or MSP430-class parts with only a few kilobytes of RAM), can fragment severely even with modest allocation patterns. Some RTOS heap implementations, such as FreeRTOS heap_2, do not coalesce at all; heap_4 and heap_5 do.
A common mitigation in deeply embedded code is to avoid dynamic allocation entirely after system initialization, or to use fixed-size memory pools (also called block allocators) that hand out chunks of one predetermined size and therefore cannot fragment. When dynamic allocation is unavoidable, allocating only a small set of distinct sizes and keeping long-lived allocations separate from short-lived ones can significantly slow the rate of fragmentation.
Detecting fragmentation requires instrumentation. Tracking the minimum ever-seen value of the largest free contiguous block alongside total free bytes reveals fragmentation that total-free-byte metrics alone would hide. The blog post "How to make a heap profiler" covers practical techniques for adding this kind of visibility to an embedded allocator.
Frequently asked
What is the difference between external and internal fragmentation?
External fragmentation is the classic
heap problem: free memory exists but is split into non-contiguous pieces that cannot satisfy a large allocation. Internal fragmentation is wasted space inside an allocated block, caused when an allocator rounds up a request to a minimum alignment or block size. Both forms waste
RAM, but external fragmentation can cause allocation failures even when total free memory appears sufficient.
Why is heap fragmentation more dangerous in embedded systems than on a desktop OS?
Desktop operating systems typically use virtual memory and, where an
MMU and paging are available, can remap physical pages to present a contiguous virtual address space to the allocator. Most embedded targets run without an MMU (or with a simple MPU that does not support page remapping), so the physical address layout of memory is fixed. There is no mechanism to defragment at runtime, and a failed malloc often has no graceful recovery path.
Will calling free() and then malloc() for the same size always reuse the freed block?
Usually, but not guaranteed. Most allocators will reuse an exact-fit or best-fit block, but alignment padding, header overhead, or allocator policy choices can cause a slightly different block to be selected. If other allocations have happened between the free and the malloc, the original block may have already been claimed. Relying on deterministic reuse is not a safe assumption.
How can I tell how fragmented my heap is at runtime?
Tracking total free bytes alone is insufficient. You need the size of the largest single free contiguous block. The ratio of largest-free-block to total-free-bytes gives a rough indicator of fragmentation: a ratio near 1.0 suggests little fragmentation; a ratio near 0 suggests severe fragmentation. This is a practical heuristic rather than a standardized metric and can be misleading in edge cases (for example, when total free memory is very small). Some allocators expose related data directly (
FreeRTOS xPortGetMinimumEverFreeHeapSize gives a low-water mark but not contiguous block size); others require custom instrumentation. The blog post 'How to make a
heap profiler' describes how to add such instrumentation to a bare-metal allocator.
Are fixed-size memory pools a complete solution to fragmentation?
They eliminate external fragmentation entirely within a pool, because every block is the same size and any free slot can satisfy any request. However, they trade that for potential internal fragmentation if objects are smaller than the pool's block size, and they require you to know the maximum number and size of concurrent allocations at design time. Mixed-size pools (one pool per object size class) can cover most use cases while keeping fragmentation bounded.
Differentiators vs similar concepts
Heap fragmentation is sometimes conflated with
stack overflow, but they are distinct failure modes.
Stack overflow occurs when the call stack grows beyond its allocated region, typically due to deep call chains or large local variables; it is addressed by increasing stack size or reducing stack depth. Heap fragmentation is a property of the dynamic allocator's free-memory layout and is not related to stack depth. The blog post "Are We Shooting Ourselves in the Foot with Stack Overflow?" covers stack overflow specifically. Heap fragmentation is also distinct from
memory leaks: a leak means memory is never freed and total free space shrinks monotonically; fragmentation can occur even when every allocation is eventually freed.