# K07 — TaskHandle_t, explicit start and static trampoline ## Position - Series: FreeRTOS C++ - Lesson: L06, card K07 - Duration: 30 minutes - One C++ owner object plus one raw verifier task - Dynamic FreeRTOS TCB/stack from real `heap_4.c` K02 introduced CRTP in a larger first-task experiment. K07 revisits the task boundary after the memory/resource cards and demands a precise identity proof: object, TCB/handle and task stack are separate things with separate lifetimes. ## Outcome After 30 minutes the student can annotate every argument of `xTaskCreate`, explain why construction and start are separate, trace `void*` back to a typed member body, and prove stable object address without virtual dispatch. ## Lesson plan — 30 minutes | Time | Mode | Evidence | | --- | --- | --- | | 0–5 | predict addresses | object vs handle/TCB vs task stack | | 5–10 | explicit start | constructor heap delta 0; start creates kernel objects | | 10–16 | C boundary | annotate entry, name, words, context, priority, handle-out | | 16–21 | trampoline | raw context equals object; static cast; private `run()` | | 21–26 | lifecycle | ready/running/completed, handle cleared, self-delete | | 26–30 | GDB/exit | stack local inside bounds; no move/vtable/destructor delete | ## Stable identity contract ```text object_before == object_after_start == xTaskCreate context == trampoline context == this in run == completed object handle_after_start == current handle == TCB address handle_after_start != C++ object address local variable address lies in task stack bounds ``` ## Why explicit start The derived object is not complete while its base constructor runs. Starting there could let the scheduler enter `run()` against an incompletely constructed object. `start()` is a separate state transition invoked only after the complete owner exists. Duplicate start is rejected without another `xTaskCreate` call. ## Wrapper contract - constructor stores name, stack words and priority only; - `start()` moves `constructed -> starting -> ready` and calls the C API; - static trampoline sets `running`, invokes `Derived::run()`, then records `completed`, clears the handle and deletes the current kernel task; - copy and move are deleted because the context address must not change; - no destructor force-deletes a live task. ## Acceptance - host test proves no constructor create call, one explicit create, stable context, duplicate rejection and `start_failed` behavior; - ABI contains the static trampoline but no vtable/RTTI/hosted runtime; - Hazard3 shows object outside `ucHeap`, TCB in `ucHeap`, stack-local inside reported task stack bounds; - counter result is 500500, wrapper ends completed with null handle; - all identity equalities and checkpoint 12 PASS hold. ## Main traps 1. `TaskHandle_t` is not the C++ object pointer; it identifies the TCB. 2. Stack depth is in `StackType_t` words, not necessarily bytes. 3. Moving a started wrapper invalidates the kernel's stored context. 4. Starting in a constructor exposes a partial object. 5. Hiding forced deletion in a destructor does not unwind arbitrary task-local C++ objects and creates unsafe asynchronous lifetime coupling.