How do you determine the appropriate stack and heap sizes for your application?
Determining stack and heap sizes is one of the most underappreciated aspects of embedded development, and getting it wrong leads to either wasted RAM (oversized allocations) or silent memory corruption (undersized allocations). The stack size must accommodate the deepest possible call chain, including all local variables, saved registers, and interrupt context. The heap size must accommodate all dynamic allocations that can be live simultaneously. Both require a combination of static analysis and runtime measurement.
For stack sizing, start with static analysis using GCC's -fstack-usage flag. This generates .su files that report the stack consumption of each function. Tools like puncover or custom scripts can parse these files and the call graph to compute the worst-case stack depth — the sum of stack usage along the deepest call path. However, static analysis has blind spots: function pointers, recursive calls, and interrupt preemption are difficult to analyze automatically. For interrupt stacking, each nested interrupt adds a fixed context frame (8 words on Cortex-M3/M4, more with FPU state), and you must account for the worst-case nesting depth. Follow static analysis with runtime measurement: paint the stack with a sentinel pattern, run the system through all stress test scenarios (maximum interrupt load, deepest function paths, worst-case input data), then check the high-water mark. Add a safety margin of 20-30% above the measured high-water mark to cover paths not exercised during testing.
For heap sizing, track peak allocation using a custom malloc wrapper that records the current and maximum heap usage. Better yet, avoid the heap entirely in safety-critical bare-metal systems — use static allocation and fixed-size pools instead. If dynamic allocation is unavoidable (e.g., for a TCP/IP stack like lwIP), the pool sizes and maximum connections define the upper bound. In the linker script, the stack and heap sizes are typically defined as symbols (_Min_Heap_Size and _Min_Stack_Size) and the linker verifies that RAM is large enough to contain .data + .bss + heap + stack. If the total exceeds available RAM, the linker produces a "region overflow" error — but this only checks the minimums, not the runtime maximums.
Source: Boot & Startup Q&A
