# K05 — FreeRTOS C++ heap bridge, heap models and `HeapStats` K05 separates three questions that are often collapsed into one: 1. Which FreeRTOS heap policy fits the deployment (`heap_1`…`heap_5`)? 2. How do all ordinary C++ allocation/deallocation forms enter one selected FreeRTOS heap domain? 3. What can current free bytes, largest block and minimum-ever free bytes actually prove? The Hazard3 executable uses the real upstream `heap_4.c`. It allocates three C++ arrays, frees the first and third, and obtains two free regions separated by the still-live middle array. A request one byte larger than the largest region fails even though the *sum* of free bytes is larger than the request. After the middle array is released, `heap_4` coalesces adjacent blocks and the same request succeeds. ## Heap policy map | Scheme | Release | Coalescence | Memory source / typical fit | | --- | --- | --- | --- | | `heap_1` | no | not applicable | one monotonic region; allocate during startup and never free | | `heap_2` | yes | no | legacy/simple reuse with fragmentation risk | | `heap_3` | libc `free` | delegated | compiler/linker `malloc` heap; requires that runtime contract | | `heap_4` | yes | adjacent blocks | one FreeRTOS region; general single-region teaching target | | `heap_5` | yes | within regions | multiple address-ordered, possibly non-contiguous regions | Only one implementation supplies `pvPortMalloc()`/`vPortFree()` in an image. The C++ bridge does not make the five policies run simultaneously. ## C++ surface [`HeapStats`](include/freertos/heap_stats.hpp) is a value snapshot of `HeapStats_t`. [`HeapWatermark`](include/freertos/heap_stats.hpp) interprets current and peak consumption relative to a captured baseline. The bridge in [`cpp_heap.cpp`](src/common/cpp_heap.cpp) supplies all six required paths: - scalar and array `operator new`; - scalar and array unsized `operator delete`; - scalar and array sized `operator delete`. This no-exceptions profile gives ordinary `new` a fail-fast contract. The controlled failure experiment therefore calls `pvPortMalloc()` directly with the malloc-failed hook disabled; it is a deliberately fallible probe, not a claim that ordinary `new` returns `nullptr`. ## Seven checkpoints | Point | Required evidence | | --- | --- | | 1 | baseline: one free block and current free bytes recorded | | 2 | three C++ allocations; payload intact; current free decreases | | 3 | first and third freed; two free blocks; total exceeds largest | | 4 | request=`largest+1` fails; successful allocation count unchanged | | 5 | middle freed; one coalesced block; current free equals baseline | | 6 | identical request now succeeds and lies in `ucHeap` | | 7 | retry freed; baseline recovered; minimum-ever still records history | On the checked RV32I build the core fragmentation evidence is: ```text available = 0x2fe0 (12256) largest = 0x27d0 (10192) request = 0x27d1 (10193) blocks = 2 peak used = 0x27f0 (10224) ``` ## Build and verify ```sh make check ``` Expected summary: ```text PASS host: HeapStats mapping and watermark history PASS ABI: complete new/delete bridge and zero hosted C++ runtime PASS task01: heap_4 fragmentation + coalescence + history ``` ## Debug ```gdb b heap_models_debug_checkpoint p g_last_checkpoint p g_baseline_stats p g_after_alloc_stats p g_fragmented_stats p g_after_failure_stats p g_coalesced_stats p g_after_retry_stats p g_final_stats p g_failure_request p g_cpp_allocation_addresses p g_cpp_deallocation_addresses p g_peak_bytes_used p g_heap_models_pass ``` ## Scope boundary `available_bytes` is a sum, not a promise of one contiguous payload. The largest free block is allocator metadata-level evidence and an allocation also needs its aligned header. K05 compares policy and measures one selected heap; runtime-selected resources and typed allocators are deferred to K06. Render the printable worksheet with `make pdf`.