# K05 — C++ heap bridge, heap models and HeapStats ## Position - Series: FreeRTOS C++ - Lesson: L04, card K05 - Duration: 30 minutes - Runtime: real FreeRTOS V11.3.0 `heap_4.c` on Hazard3/RV32I - C++ mode: freestanding C++17, no exceptions, RTTI or hosted `libstdc++` - New surface: `HeapStats`, `HeapWatermark`, complete `new`/`delete` bridge K03 proved one-owner RAII, while K05 now exposes the allocator policy and its metrics. It does not introduce runtime resource selection; K06 will build that on top of the explicit failure and alignment contract established here. ## Outcome After 30 minutes the student can choose among `heap_1`…`heap_5` for a stated memory topology/lifetime policy, enumerate all C++ allocation bridge forms, distinguish total free bytes from largest free block, and prove that minimum-ever free bytes retain history after current free returns to baseline. ## Lesson plan — 30 minutes | Time | Mode | Evidence | | --- | --- | --- | | 0–5 | choose policy | justify heap scheme for three deployments | | 5–9 | bridge | trace `new[] -> pvPortMalloc` and `delete[] -> vPortFree` | | 9–14 | predict | draw A/B/C blocks and predicted stats after freeing A/C | | 14–21 | Hazard3 | prove `request < total`, `request > largest`, failure | | 21–26 | coalescence | free B; retry same request; inspect one free block | | 26–30 | history/exit | baseline recovery versus minimum-ever history | ## Policy table | Scheme | Free? | Coalesce? | Key deployment constraint | | --- | --- | --- | --- | | heap_1 | no | n/a | monotonic startup allocation only | | heap_2 | yes | no | holes remain separate; fragmentation grows | | heap_3 | libc | libc | linker/compiler must provide a suitable heap | | heap_4 | yes | yes | one FreeRTOS-owned region | | heap_5 | yes | yes | regions defined in increasing address order before allocation | Student choices: 1. All objects allocated once at startup, never deleted: `heap_1` is a valid simplest policy. 2. One freestanding RAM region, dynamic tasks/queues with deletion: `heap_4`. 3. Two disjoint RAM banks that must both feed the kernel allocator: `heap_5`. `heap_3` is not treated as a portable freestanding default because it delegates storage and behavior to the compiler C library and linker heap. ## Canonical experiment ```text baseline: [ free ........................................ ] allocate: [ A 2K ][ B 4K ][ C 2K ][ free ............... ] free A/C: [ free ][ B live ][ free + trailing free ...... ] total=12256, largest=10192, blocks=2 request 10193 -> FAIL (sum is enough, no individual block is) free B: [ one coalesced free block ..................... ] retry 10193 -> SUCCESS free retry -> current free returns to baseline minimum-ever remains at the historical low ``` Exact values may change with alignment/header configuration. Assessment uses relations, except for the fixed requested payload sizes in the source. ## Stable evidence contract | Evidence | Required relation | | --- | --- | | C++ addresses | three distinct, aligned addresses inside `ucHeap` | | bridge counters | allocations=3, deallocations=3, live=0 | | deallocation order | A, C, B | | fragmented stats | free blocks >=2 and total > largest | | failed request | largest < request < total; successful allocations unchanged | | coalesced stats | free blocks=1; largest=current=baseline | | retry | same request succeeds inside `ucHeap` | | final stats | current=baseline, minimum-ever remains below current | ## Complete bridge contract ```cpp void* operator new(size_t); void* operator new[](size_t); void operator delete(void*) noexcept; void operator delete[](void*) noexcept; void operator delete(void*, size_t) noexcept; void operator delete[](void*, size_t) noexcept; ``` Ordinary `new` is fail-fast in this course profile because exceptions are off. The recoverable experiment uses raw `pvPortMalloc` and checks `nullptr`. ## Misconceptions 1. Total free bytes do not imply one payload of that size can be allocated. 2. Returning to the current-free baseline does not erase the minimum-ever watermark. 3. `heap_4` limits external fragmentation by coalescing; it cannot promise absence of all fragmentation while live blocks separate holes. 4. A C++ bridge selects one heap domain; it does not merge five schemes. 5. `heap_3` inherits the C library/linker heap contract and is therefore not a self-contained freestanding answer. ## Acceptance - host test verifies field mapping and current/peak watermark calculations; - ABI test finds all six allocation functions and no hosted C++ runtime; - Hazard3 reaches all seven checkpoints and PASS; - failure request lies strictly between largest block and total free bytes; - retry succeeds only after B is freed and blocks coalesce; - student chooses heap_1, heap_4 and heap_5 for the three stated scenarios and justifies each constraint.