Dynamic and Variable-Length Arrays in Embedded C
In traditional C89/C90 programming, array sizes must be compile-time constants (for example, int buf[10];) because memory is allocated statically. Modern embedded systems, however, frequently require flexibility to handle data sizes that vary at runtime.
In Embedded C, this requirement is typically addressed using two approaches:
- Heap-based dynamic allocation
- C99 Variable-Length Arrays (VLAs)
Each approach has different implications for safety, predictability, and system reliability.
🧠 Heap-Based Dynamic Arrays (malloc / free)
#
Heap-based allocation is the most portable and widely supported method for allocating memory at runtime in C.
Mechanism #
Memory is requested from the heap using malloc() and must be explicitly released using free() once it is no longer needed.
Characteristics #
-
Advantages
- Supported by nearly all C standards and embedded toolchains
- Capable of allocating large buffers, limited mainly by available RAM
- Object lifetime is explicitly controlled by the application
-
Limitations
- Susceptible to memory fragmentation in long-running systems
- Allocation and deallocation times are non-deterministic
- Errors such as memory leaks and double frees can lead to system instability
For real-time and safety-critical systems, heap usage is often restricted or completely avoided.
🧮 C99 Variable-Length Arrays (VLA) #
The C99 standard introduced Variable-Length Arrays, allowing array sizes to be determined at runtime while remaining stack-allocated.
void process_data(int n)
{
int buffer[n]; /* Size determined at runtime */
/* ... use buffer ... */
}
Mechanism #
- Memory is allocated on the stack when the function is entered
- Memory is automatically reclaimed when the function returns
Characteristics #
-
Advantages
- Simple syntax and improved code readability
- No explicit memory management required
- Faster than heap allocation due to minimal overhead
-
Limitations
- High risk of stack overflow on systems with limited stack space
- Stack usage is difficult to analyze and bound statically
- Support became optional starting with the C11 standard
VLAs exchange convenience for reduced memory safety and predictability.
📊 Comparison in Embedded Systems #
| Aspect | Heap Allocation (malloc) |
Variable-Length Array (VLA) |
|---|---|---|
| Memory location | Heap | Stack |
| Lifetime | Until free() is called |
Function scope |
| Standard support | C89 and later | C99 (optional in C11+) |
| Timing determinism | Poor | Good |
| Primary risk | Fragmentation, leaks | Stack overflow |
⚠️ Critical Considerations for Embedded Developers #
-
Stack Size Constraints Many microcontrollers (such as STM32, AVR, and ESP32) have stack sizes measured in kilobytes. A single oversized VLA can overwrite adjacent memory regions and cause immediate system failure.
-
Real-Time Determinism Heap allocation introduces unpredictable latency, making it unsuitable for hard real-time execution paths. VLAs avoid timing jitter but provide no protection against excessive stack usage.
-
MISRA C and Safety Standards MISRA C explicitly forbids the use of VLAs, as stack usage cannot be reliably bounded at compile time. As a result, VLAs are unacceptable in automotive, medical, railway, and aerospace systems.
-
Evolution of the C Standard Although C99 mandated compiler support for VLAs, C11 and later standards made them optional. Many safety-focused toolchains deliberately disable VLA support.
🎯 Summary #
While C99 Variable-Length Arrays offer syntactic convenience for temporary buffers, they introduce significant risks in embedded environments with constrained stack memory.
For robust Embedded C systems:
- Prefer fixed-size arrays when maximum size is known
- Use heap allocation only when necessary and with strict error handling
- Assume VLAs are unavailable in safety-critical or MISRA-compliant projects
In embedded and real-time development, memory predictability is more important than convenience.