HomeAbout UsContact Us

FreeRTOS Heap Implementations Compared: Heap_1 to Heap_5

By Jithin Tom
Published in Embedded OS
July 14, 2026
5 min read
FreeRTOS Heap Implementations Compared: Heap_1 to Heap_5

Table Of Contents

01
Heap_1: Allocate Only, Never Free
02
Heap_2: Allocate and Free, No Coalescing
03
Heap_3: Standard Library Wrapper
04
Heap_4: Coalescing Free Blocks, Single Contiguous Region
05
Heap_5: Multiple Non-Contiguous Heap Regions
06
Decision Matrix
07
Selection Guidelines
08
Practical Configuration Tips
09
Summary
10
Related Reading
11
References
12
Frequently Asked Questions

Dynamic memory allocation in bare-metal and RTOS-based firmware remains a polarizing topic. Safety-critical standards like MISRA C and ISO 26262 discourage or forbid heap usage after initialization. Yet many embedded Linux, RTOS, and application-layer firmware stacks rely on dynamic allocation for protocol buffers, command queues, and variable-length data structures. FreeRTOS acknowledges this reality by shipping five heap implementations (Heap_1 through Heap_5) in Source/portable/MemMang/, each with distinct fragmentation behavior, determinism guarantees, and memory overhead characteristics.

Choosing the wrong heap scheme manifests as silent memory corruption, allocation failures after hours of uptime, or priority inversion when a low-priority task holds a block the high-priority task needs. This article dissects all five implementations, their failure modes, and the decision criteria for selecting the right one.

Heap_1: Allocate Only, Never Free

File: heap_1.c
API: pvPortMalloc() only — no vPortFree()

Heap_1 implements a simple bump-pointer allocator. The heap is a single static array (ucHeap[configTOTAL_HEAP_SIZE]). A pointer tracks the next free byte. Each allocation rounds up to portBYTE_ALIGNMENT, returns the current pointer, and advances it by the requested size. It stores no block headers. Free does nothing.

/* heap_1.c -- simplified core logic with alignment support */
static uint8_t ucHeap[configTOTAL_HEAP_SIZE];
static size_t xNextFreeByte = 0;
void *pvPortMalloc(size_t xWantedSize) {
void *pvReturn = NULL;
/* Ensure allocation size is aligned */
if ((xWantedSize & portBYTE_ALIGNMENT_MASK) != 0x00) {
xWantedSize += (portBYTE_ALIGNMENT - (xWantedSize & portBYTE_ALIGNMENT_MASK));
}
vTaskSuspendAll();
{
if ((xNextFreeByte + xWantedSize) <= configTOTAL_HEAP_SIZE) {
pvReturn = &ucHeap[xNextFreeByte];
xNextFreeByte += xWantedSize;
}
}
xTaskResumeAll();
return pvReturn;
}
void vPortFree(void *pv) { (void)pv; } /* No-op */

Characteristics:

  • Zero fragmentation — memory only grows
  • Deterministic O(1) allocation
  • Zero overhead per block (just alignment padding, no block headers)
  • Fatal limitation: Cannot free memory. Total allocations must stay within configTOTAL_HEAP_SIZE for the entire uptime.

Use case: Safety-critical systems where all objects are created at startup and live forever. Bootloaders, simple state machines, certification-friendly profiles.

Failure mode: pvPortMalloc() returns NULL once the heap is exhausted. No recovery possible without reset.

+--------------------------------------------------------------+
| HEAP_1 MEMORY MAP |
+--------------------------------------------------------------+
| Block A (64B) | Block B (128B) | Block C (32B) | FREE SPACE |
+--------------------------------------------------------------+
^ ^
| |
Heap Start Next Free

Heap_2: Allocate and Free, No Coalescing

File: heap_2.c
API: pvPortMalloc(), vPortFree()

Heap_2 adds a free list. Each block (allocated or free) carries a BlockLink_t header containing the block size and a pointer to the next free block. Free inserts the block into the free list in size-sorted order (smallest first); allocate scans the free list for a fit, resulting in a best-fit allocation strategy.

/* heap_2.c -- simplified BlockLink_t and allocation */
typedef struct A_BLOCK_LINK {
struct A_BLOCK_LINK *pxNextFreeBlock;
size_t xBlockSize;
} BlockLink_t;
static BlockLink_t xStart;
static BlockLink_t *pxEnd = NULL;
void *pvPortMalloc(size_t xWantedSize) {
BlockLink_t *pxBlock, *pxPreviousBlock, *pxNewBlockLink;
void *pvReturn = NULL;
/* Ensure allocation size is aligned and includes the block header */
if ((xWantedSize & portBYTE_ALIGNMENT_MASK) != 0x00) {
xWantedSize += (portBYTE_ALIGNMENT - (xWantedSize & portBYTE_ALIGNMENT_MASK));
}
xWantedSize += xHeapStructSize;
vTaskSuspendAll();
{
pxPreviousBlock = &xStart;
pxBlock = xStart.pxNextFreeBlock;
/* Scan the free list (which is sorted by block size) */
while ((pxBlock->xBlockSize < xWantedSize) && (pxBlock != pxEnd)) {
pxPreviousBlock = pxBlock;
pxBlock = pxBlock->pxNextFreeBlock;
}
if (pxBlock != pxEnd) {
/* Return pointer to payload (jumps over the header structure) */
pvReturn = (void *)((uint8_t *)pxBlock + xHeapStructSize);
/* Remove the block from the free list */
pxPreviousBlock->pxNextFreeBlock = pxBlock->pxNextFreeBlock;
/* Split the block if the remainder is large enough */
if ((pxBlock->xBlockSize - xWantedSize) > heapMINIMUM_BLOCK_SIZE) {
pxNewBlockLink = (BlockLink_t *)((uint8_t *)pxBlock + xWantedSize);
pxNewBlockLink->xBlockSize = pxBlock->xBlockSize - xWantedSize;
pxBlock->xBlockSize = xWantedSize;
/* Insert the split block back into the free list (sorted by size) */
prvInsertBlockIntoFreeList(pxNewBlockLink);
}
}
}
xTaskResumeAll();
return pvReturn;
}

Characteristics:

  • Supports free(), enabling object lifecycles
  • No coalescing — adjacent free blocks remain separate
  • Fragmentation grows monotonically with allocate/free cycles
  • Best-fit allocation: O(n) scan of size-sorted free list
  • ~8 bytes overhead per block (header)

Use case: Systems with bounded allocation patterns where objects are created/destroyed but total concurrent allocations stay within a predictable envelope.

Failure mode: External fragmentation. Many small free blocks exist but no single block satisfies a large request. Allocation fails with NULL despite ample total free bytes.

+--------------------------------------------------------------+
| HEAP_2 MEMORY MAP (FRAGMENTED) |
+--------------------------------------------------------------+
| A(64) | FREE(32) | B(128) | FREE(16) | C(32) | FREE(200) | ...
+--------------------------------------------------------------+
^ ^ ^
| | |
Cannot satisfy Cannot satisfy Only large block
64B request 48B request but fragmented
(two 32B free) (16+? free)

Heap_3: Standard Library Wrapper

File: heap_3.c
API: pvPortMalloc(), vPortFree() → wraps malloc()/free()

Heap_3 is a thin thread-safety wrapper around the compiler’s standard library heap. Every pvPortMalloc() calls vTaskSuspendAll(), then malloc(), then xTaskResumeAll(). Free does the same with free().

/* heap_3.c -- entire implementation */
void *pvPortMalloc(size_t xWantedSize) {
void *pvReturn;
vTaskSuspendAll();
pvReturn = malloc(xWantedSize);
xTaskResumeAll();
return pvReturn;
}
void vPortFree(void *pv) {
vTaskSuspendAll();
free(pv);
xTaskResumeAll();
}

Characteristics:

  • Delegates fragmentation behavior to libc (typically dlmalloc, ptmalloc, or newlib-nano)
  • Non-deterministic allocation latency — scheduler suspended for unbounded time
  • Standard library overhead: 8-16 bytes per block + allocator metadata
  • Requires linker-provided heap region (usually .heap or __heap_start/__heap_end)
  • Thread-safe via scheduler suspension, not mutex

Use case: Porting existing code that calls malloc/free directly; prototypes; non-real-time tasks.

Failure mode: Priority inversion during long malloc/free inside vTaskSuspendAll(). High-priority tasks blocked for indeterminate time. Also inherits libc fragmentation behavior.

+--------------------------------------------------------------+
| HEAP_3 EXECUTION FLOW |
+--------------------------------------------------------------+
| Task A (high prio) |
| pvPortMalloc(64) |
| vTaskSuspendAll() ---- SCHEDULER SUSPENDED ---- |
| malloc(64) [unbounded time, may scan free lists] |
| xTaskResumeAll() ---- SCHEDULER RESUMED ---- |
| |
| Task B (low prio) -- BLOCKED entire duration -- |
+--------------------------------------------------------------+

Heap_4: Coalescing Free Blocks, Single Contiguous Region

File: heap_4.c
API: pvPortMalloc(), vPortFree()

Heap_4 is the most widely used implementation for general-purpose FreeRTOS applications. It maintains a free list sorted by address. On vPortFree(), it checks whether the freed block is adjacent to the previous or next free block and coalesces them into a single larger block. This dramatically reduces external fragmentation compared to Heap_2.

/* heap_4.c -- coalescing logic in vPortFree */
void vPortFree(void *pv) {
BlockLink_t *pxBlockToFree = (BlockLink_t *)((uint8_t *)pv - xHeapStructSize);
/* Clear the allocated status bit (MSB of xBlockSize) */
pxBlockToFree->xBlockSize &= ~((size_t)1 << ((sizeof(size_t) * 8) - 1));
vTaskSuspendAll();
{
/* Find the block in the free list */
BlockLink_t *pxIterator = &xStart;
while (pxIterator->pxNextFreeBlock < pxBlockToFree) {
pxIterator = pxIterator->pxNextFreeBlock;
}
/* Coalesce with previous free block if adjacent */
if ((uint8_t *)pxIterator + pxIterator->xBlockSize == (uint8_t *)pxBlockToFree) {
pxIterator->xBlockSize += pxBlockToFree->xBlockSize;
pxBlockToFree = pxIterator;
} else {
pxBlockToFree->pxNextFreeBlock = pxIterator->pxNextFreeBlock;
pxIterator->pxNextFreeBlock = pxBlockToFree;
}
/* Coalesce with next free block if adjacent */
if ((uint8_t *)pxBlockToFree + pxBlockToFree->xBlockSize ==
(uint8_t *)pxBlockToFree->pxNextFreeBlock) {
pxBlockToFree->xBlockSize += pxBlockToFree->pxNextFreeBlock->xBlockSize;
pxBlockToFree->pxNextFreeBlock = pxBlockToFree->pxNextFreeBlock->pxNextFreeBlock;
}
}
xTaskResumeAll();
}

Characteristics:

  • Coalesces adjacent free blocks — combats external fragmentation
  • Single contiguous heap region (ucHeap[configTOTAL_HEAP_SIZE])
  • First-fit allocation from address-sorted free list
  • ~8 bytes overhead per block
  • Deterministic-ish: O(n) free list scan, but n = free block count (typically small)

Use case: General-purpose FreeRTOS applications with dynamic object lifecycles. Default choice for most projects.

Failure mode: Internal fragmentation (allocation rounding) + residual external fragmentation when allocation patterns create many small holes that cannot be coalesced (non-adjacent frees). Large allocations may still fail despite free memory.

+--------------------------------------------------------------+
| HEAP_4 COALESCING EXAMPLE |
+--------------------------------------------------------------+
| BEFORE FREE: |
| [A:64][FREE:32][B:128][FREE:16][C:32][FREE:200] |
| |
| vPortFree(B) -- coalesces with adjacent FREE blocks: |
| |
| AFTER FREE: |
| [A:64][FREE:376][C:32] |
| ^^^^^^^^^^^ merged into single 376-byte free block |
+--------------------------------------------------------------+

Heap_5: Multiple Non-Contiguous Heap Regions

File: heap_5.c
API: pvPortMalloc(), vPortFree(), vPortDefineHeapRegions()

Heap_5 extends Heap_4 by allowing the heap to span multiple disjoint memory regions. This is essential for MCUs with multiple RAM banks (e.g., STM32H7: SRAM1/SRAM2/SRAM3/AXI SRAM/ITC/DTC), where the largest contiguous region may be smaller than the total available RAM.

/* heap_5.c -- region definition */
typedef struct HeapRegion {
uint8_t *pucStartAddress;
size_t xSizeInBytes;
} HeapRegion_t;
HeapRegion_t xHeapRegions[] = {
{ (uint8_t *)0x20000000, 0x20000 }, /* SRAM1: 128 KB */
{ (uint8_t *)0x20020000, 0x10000 }, /* SRAM2: 64 KB */
{ (uint8_t *)0x30000000, 0x40000 }, /* AXI SRAM: 256 KB */
{ NULL, 0 } /* Terminator */
};
void vPortDefineHeapRegions(const HeapRegion_t *const pxHeapRegions) {
/* Validate alignment, sort by address, build free list */
}

Characteristics:

  • All Heap_4 features (coalescing, first-fit)
  • Multiple regions — each independently managed but part of same allocator
  • Regions must be aligned to portBYTE_ALIGNMENT
  • vPortDefineHeapRegions() must be called before any allocation (typically in main() before scheduler start)
  • Same ~8 bytes/block overhead

Use case: Multi-bank RAM MCUs (STM32H7, NXP i.MX RT, TI TM4C, ESP32-S3 with external PSRAM), applications needing large total heap but no single contiguous region.

Failure mode: Same fragmentation risks as Heap_4, distributed across regions. A large allocation may fail even if total free space is sufficient, if no single region has a large enough block.

+--------------------------------------------------------------+
| HEAP_5 MULTI-REGION MAP |
+--------------------------------------------------------------+
| REGION 1 (SRAM1, 128KB) | REGION 2 (SRAM2, 64KB) |
| +----------------------------+ | +------------------------+ |
| | TaskA(32K) | FREE(64K) | | | FREE(64K) | |
| +----------------------------+ | +------------------------+ |
| | |
| REGION 3 (AXI SRAM, 256KB) |
| +----------------------------------------------------------+ |
| | TaskB(100K) | FREE(156K) | |
| +----------------------------------------------------------+ |
| |
| pvPortMalloc(200K) --> FAILS (no single region has 200K) |
| pvPortMalloc(80K) --> SUCCEEDS (allocates from Region 3) |
+--------------------------------------------------------------+

Decision Matrix

CriterionHeap_1Heap_2Heap_3Heap_4Heap_5
free() support
CoalescingN/Alibc
Multi-region
Deterministic alloc✅ O(1)⚠️ O(n)⚠️ O(n)⚠️ O(n)
Fragmentation riskNoneHighlibcLowLow
Overhead/block0B~8Blibc~8B~8B

Selection Guidelines

  1. Safety-critical, no runtime allocation after init → Heap_1
  2. Bounded dynamic objects, no large allocations after startup → Heap_2
  3. Porting legacy malloc/free code, non-real-time tasks → Heap_3
  4. General-purpose FreeRTOS application (default choice) → Heap_4
  5. Multi-bank RAM MCU, total heap > largest contiguous region → Heap_5

Practical Configuration Tips

Heap_4/5 sizing: Set configTOTAL_HEAP_SIZE (Heap_4) or sum of region sizes (Heap_5) to leave 15-20% headroom above peak observed usage. Monitor with xPortGetFreeHeapSize() and xPortGetMinimumEverFreeHeapSize().

Heap_5 region ordering: Regions must be declared in start-address order (from lowest address to highest address). Heap_5 does not sort them and will fail an assertion (configASSERT( ( size_t ) xAddress > ( size_t ) pxEnd )) or corrupt memory if defined out-of-order. Additionally, each region must be individually aligned to portBYTE_ALIGNMENT (typically 8 bytes on Cortex-M).

Debugging fragmentation: Enable configUSE_MALLOC_FAILED_HOOK to catch allocation failures. For Heap_4 and Heap_5, call vPortGetHeapStats() (introduced in FreeRTOS v10.1.0) to get a HeapStats_t structure detailing the number of free blocks, the size of the largest/smallest free blocks, and the allocation/free call counters. This avoids having to write custom code to walk the private free list structures.

Thread safety: All five implementations use vTaskSuspendAll/xTaskResumeAll for mutual exclusion. Because they rely on scheduler suspension, none of the standard FreeRTOS heaps are ISR-safe. You must never call pvPortMalloc() or vPortFree() from an Interrupt Service Routine. For ISR-safe memory management, use a lock-free ring buffer, a statically allocated pool, or defer the processing to a task via xQueueSendFromISR or task notifications.

Summary

FreeRTOS’s five heap implementations form a spectrum from zero-overhead bump allocation (Heap_1) to multi-region coalescing allocators (Heap_5). The choice is not merely about whether you need free() — it’s about fragmentation tolerance, determinism requirements, and physical memory topology. Heap_4 is the correct default for most applications. Heap_5 is mandatory when your MCU’s RAM is split across non-contiguous banks and you need a single logical heap larger than any one bank. Heap_1 and Heap_2 serve safety-critical niches where certification demands zero fragmentation. Heap_3 is a migration aid, not a production allocator for real-time tasks.

Measure your actual allocation patterns under load. Enable the malloc failed hook. Profile xPortGetMinimumEverFreeHeapSize() over 24-hour soak tests. The heap that works on the bench at 20% load may fragment catastrophically at 80% load with a different allocation sequence.

References

  1. FreeRTOS Kernel Developer Guide — Memory Management, https://www.freertos.org/a00111.html
  2. Barry, R. “FreeRTOS Reference Manual — Heap Memory Management,” Real Time Engineers Ltd., 2024.
  3. STMicroelectronics. “STM32H7 Series Reference Manual — Memory Map,” RM0433 Rev 7, https://www.st.com/en/microcontrollers-microprocessors/stm32h7-series/documentation.html
  4. Ganssle, J. “The Art of Designing Embedded Systems,” 2nd ed., Elsevier, 2008, Ch. 7 “Memory Management.”
  5. MISRA C:2012 Amendment 3, Rule 21.3: “Dynamic memory allocation shall not be used after initialization.”
  6. Douglass, B. “Real-Time Agility: The Harmony/ESW Method for Real-Time Systems,” Addison-Wesley, 2016, Sec. 9.4 “Heap Alternatives.”

Frequently Asked Questions

Which FreeRTOS heap implementation should I use for a safety-critical system?

Heap_1 or Heap_2 are preferred for safety-critical systems because they never free memory, eliminating fragmentation risk. Heap_1 only allows allocation; Heap_2 adds free() but still cannot coalesce adjacent free blocks.

What causes fragmentation in Heap_4 and how does Heap_5 solve it?

Heap_4 coalesces adjacent free blocks but cannot relocate allocated blocks. Heap_5 supports multiple non-contiguous memory regions, allowing you to place heap sections in different RAM banks and reduce pressure on any single region.

Can I use Heap_3 with thread-safe malloc/free in a multi-threaded FreeRTOS application?

Heap_3 wraps the standard library malloc/free with vTaskSuspendAll/xTaskResumeAll for thread safety. It works but adds scheduler suspension overhead on every allocation. For high-frequency allocations, Heap_4 or Heap_5 with pvPortMalloc/vPortFree is faster.

How do I configure Heap_5 for multiple non-contiguous memory regions?

Call vPortDefineHeapRegions() with an array of HeapRegion_t structures before any allocation. Each structure defines a base address and length in bytes. The array must be terminated with a {NULL, 0} entry. Regions must be aligned to portBYTE_ALIGNMENT.

What is the memory overhead of each FreeRTOS heap implementation?

Heap_1: 0 bytes per allocation (no block header, just alignment padding). Heap_2, Heap_4, and Heap_5: ~8 bytes per block for the metadata header (which also serves as the free list node when freed). Heap_3: compiler stdlib overhead. Heap_5 also requires a small array of HeapRegion_t structures at startup.

Tags

freertosheapmemory-managementheap_4heap_5fragmentationembedded-os

Share


Previous Article
ARM Cortex-M NVIC Interrupt Latency Optimization
Jithin Tom

Jithin Tom

A Closer Look at C/C++, RTOS, and Embedded Systems

Related Posts

FreeRTOS Task Notification Latency vs Queue Performance
FreeRTOS Task Notification Latency vs Queue Performance
July 09, 2026
2 min
© 2026, All Rights Reserved.
Powered By Netlyft

Quick Links

Advertise with usAbout UsContact Us

Social Media