Stack and heap memory are two important areas of RAM used by microcontrollers to manage program data during execution. Stack memory automatically manages function calls, local variables and return addresses, while heap memory supports dynamic memory allocation through functions such as malloc() and free() in C. Understanding stack and heap memory helps embedded developers optimize RAM usage, prevent stack overflow, avoid heap fragmentation and build reliable embedded systems.Memory management is one of the most important aspects of embedded systems programming. Unlike desktop computers, microcontrollers usually operate with limited RAM, processing power and storage capacity. Developers must carefully manage available memory to ensure that firmware runs efficiently and reliably. When programming microcontrollers using Embedded C, two important memory areas are the stack and heap. Both use RAM, but they serve different purposes during program execution. Stack memory is mainly responsible for managing function calls, local variables, parameters and return addresses. Heap memory, on the other hand, supports dynamic memory allocation when a program needs memory at runtime. Understanding the difference between stack memory and heap memory is essential when working with STM32, ARM Cortex-M, ESP32 and other microcontroller platforms. In this article, we will explore stack and heap memory, how they work in Embedded C, their differences, memory allocation techniques, common problems and practical memory optimization strategies for embedded developers.
Before understanding stack and heap memory, it is important to understand the memory architecture of microcontrollers.
A typical microcontroller contains different types of memory, each designed for a specific purpose.
Stack and heap memory are generally allocated within the SRAM region.
A typical SRAM layout includes:
| Memory Section | Purpose |
.data | Stores initialized global and static variables |
.bss | Stores uninitialized or zero-initialized global and static variables |
| Heap | Supports dynamic memory allocation |
| Stack | Stores function calls, local variables and return information |
The exact memory arrangement depends on the microcontroller architecture, compiler, linker script and runtime environment.
For example, an STM32 microcontroller may have 128 KB, 256 KB or more SRAM depending on the specific model. The stack, heap, global variables and other runtime data must share the available RAM.
This is why proper memory allocation in microcontrollers is important.
Stack memory is a region of RAM used to manage function execution. It follows the Last In, First Out (LIFO) principle.
This means the last function called is generally the first function to finish executing.
Whenever a function is called, the processor and compiler-generated code manage the required execution information using the stack.
Stack memory typically stores:
The exact information stored on the stack depends on the processor architecture and compiler.
Consider the following Embedded C program:
#include
void calculate(void)
{
int a = 10;
int b = 20;
int result = a + b;
printf("Result: %d\n", result);
}
int main(void)
{
calculate();
return 0;
}Step-by-step execution:
main().main() function calls calculate().a, b and result may be stored in the stack frame.main().Heap memory is a region of RAM used for dynamic memory allocation during program execution.
Unlike stack memory, heap memory allows developers to request memory when required and release it when it is no longer needed.
In Embedded C, dynamic memory allocation is commonly performed using:
malloc()calloc()realloc()free()These functions are available through the standard C library header stdlib.h.
Consider this example:
#include
#include
int main(void)
{
int *ptr;
ptr = malloc(5 * sizeof(int));
if (ptr == NULL)
{
printf("Memory allocation failed\n");
return 1;
}
for (int i = 0; i < 5; i++)
{
ptr[i] = (i + 1) * 10;
}
for (int i = 0; i < 5; i++)
{
printf("%d\n", ptr[i]);
}
free(ptr);
return 0;
}Explanation:
malloc() requests memory from the heap.5 * sizeof(int) calculates the required memory size.ptr.free(ptr) releases the allocated memory.If memory allocation fails, malloc() returns NULL. Therefore, checking the returned pointer is important.
Understanding the difference between stack and heap memory helps embedded developers select the appropriate memory allocation strategy.
| Feature | Stack Memory | Heap Memory |
| Allocation | Automatic | Dynamic |
| Deallocation | Automatic | Usually manual |
| Memory usage | Function execution data | Runtime allocated data |
| Speed | Generally faster | Generally slower |
| Management | Managed through function execution | Managed through allocation functions |
| Lifetime | Usually tied to function execution | Until released or program termination |
| Fragmentation | Not subject to external heap fragmentation | Can suffer from fragmentation |
| Size | Limited by configured stack size | Limited by available heap and allocator |
| Common issue | Stack overflow | Memory leak and fragmentation |
| C functions | Function calls and local variables | malloc(), calloc(), realloc(), free() |
Memory allocation in Embedded C can be broadly divided into static and dynamic allocation.
Understanding these two approaches is important for managing RAM usage in embedded systems.
Static memory allocation reserves memory before program execution or for the lifetime of the program.
It is commonly used for global variables, static variables and fixed-size data structures.
Example:
#include
int sensor_data[100];
int main(void)
{
sensor_data[0] = 25;
printf("%d", sensor_data[0]);
return 0;
}Here, sensor_data is a global array. Its memory is reserved in the program’s static storage area, typically .bss or .data, rather than on the stack or heap.
Dynamic memory allocation allows programs to request memory during runtime.
Example:
#include
int main(void)
{
int *sensor_data;
sensor_data = malloc(100 * sizeof(int));
if (sensor_data == NULL)
{
return 1;
}
sensor_data[0] = 25;
free(sensor_data);
return 0;
}In this example, memory is requested from the heap when the program runs.
| Parameter | Static Allocation | Dynamic Allocation |
| Allocation time | Before or at program startup | During runtime |
| Memory size | Fixed | Can be selected at runtime |
| Management | Automatic lifetime for static objects | Manual allocation and release |
| Flexibility | Lower | Higher |
| Predictability | Generally high | Depends on allocator |
| Common use | Fixed buffers and sensor arrays | Variable-sized data structures |
For many microcontroller applications, static memory allocation is preferred because it provides more predictable memory usage.
However, dynamic memory allocation can still be useful when implemented with appropriate controls.
Dynamic memory allocation provides flexibility, but it can introduce challenges in microcontroller programming.
Many embedded systems operate continuously and must respond to external events within strict timing requirements. Uncontrolled heap allocation can make memory usage and execution time less predictable.
Here are the main reasons why embedded developers often avoid frequent dynamic memory allocation.
Heap fragmentation occurs when memory becomes divided into smaller free and allocated blocks after repeated allocation and deallocation.
For example, imagine a microcontroller has 1 KB of available heap memory. A program allocates several memory blocks and later releases some of them.
The free memory may become scattered across different locations. Even if the total free memory is sufficient, a large continuous memory request may fail because there is no single block large enough.
How to reduce heap fragmentation:
A memory leak occurs when dynamically allocated memory is no longer required but is not released.
Example:
#include
void process_data(void)
{
int *ptr = malloc(100 * sizeof(int));
if (ptr == NULL)
{
return;
}
/* Process data */
// Missing free(ptr)
}Every time process_data() executes, it may allocate additional heap memory without releasing the previous allocation.
Over time, available heap memory decreases, potentially causing allocation failures.
Solution: Release dynamically allocated memory when it is no longer required.
free(ptr);
ptr = NULL;Stack overflow occurs when a program uses more stack memory than the available stack space.
Common causes include:
Example:
void process_sensor(void)
{
int buffer[5000];
buffer[0] = 100;
}If an integer occupies 4 bytes, this array requires approximately 20 KB of memory.
If the configured stack is smaller than the required stack usage, the program may experience stack overflow or memory corruption.
Prevention techniques:
STM32 microcontrollers are widely used in industrial automation, automotive electronics, IoT devices and embedded product development.
Most STM32 microcontrollers use ARM Cortex-M processors and have limited SRAM compared with desktop systems.
Understanding stack and heap memory in STM32 is important when developing firmware using STM32CubeIDE, GCC or other embedded development environments.
A typical STM32 application may organize SRAM into the following sections:
| Memory Section | Description |
.data | Initialized global and static variables |
.bss | Uninitialized global and static variables |
| Heap | Memory used for dynamic allocation |
| Stack | Function execution and temporary data |
The linker script defines important memory regions and symbols used by the runtime environment.
In STM32CubeIDE projects, stack and heap sizes can commonly be configured through the linker script or project settings, depending on the toolchain and project template.
For example, a linker script may define:
_Min_Heap_Size = 0x200;
_Min_Stack_Size = 0x400;Here:
0x200 represents 512 bytes of minimum heap space.0x400 represents 1024 bytes of minimum stack space.These values are examples, not universal STM32 recommendations. Actual memory requirements depend on the application.
Efficient memory management helps embedded developers build stable firmware that operates reliably with limited RAM.
The following techniques can help optimize memory usage in microcontrollers.
Use fixed-size arrays and static buffers when the required data size is known in advance.
This reduces dependency on runtime heap allocation.
Large local arrays can consume significant stack memory.
Instead, consider using static or global buffers when their lifetime and sharing requirements make that approach appropriate.
Check stack consumption during development.
Tools such as compiler-generated stack reports, linker map files, stack watermarking and debugger inspection can help identify excessive stack usage.
Avoid unnecessary calls to malloc() and free() in time-critical embedded applications.
If dynamic allocation is required, consider using fixed-size memory pools or dedicated allocators.
Choose appropriate data types based on the required value range.
For example, using an 8-bit unsigned integer instead of a 32-bit integer may reduce memory consumption when the value range permits it.
Recursive functions can create additional stack frames for every function call.
In memory-constrained microcontrollers, iterative approaches may provide more predictable stack usage.
When using FreeRTOS or another real-time operating system, each task requires its own stack.
Developers must calculate task stack requirements based on local variables, function calls, interrupts and runtime behavior.
Stack and heap memory are used in different ways depending on the embedded application.
| Application | Stack Memory Usage | Heap Memory Usage |
| IoT Devices | Function calls and sensor processing | Dynamic network buffers |
| Automotive Systems | Function execution and control logic | Controlled runtime data allocation |
| Industrial Automation | Control functions and local variables | Communication data structures |
| Robotics | Sensor processing and calculations | Dynamic data structures |
| Embedded AI | Function execution and inference operations | Runtime tensor or model buffers, depending on framework |
| RTOS Applications | Task execution and local variables | Task or object allocation, depending on configuration |
For example, an IoT device collecting temperature and humidity data may use stack memory for sensor-reading functions and fixed-size buffers for data processing.
A more complex application may use dynamically allocated network buffers, but developers must ensure that memory usage remains predictable.
Choosing between stack and heap memory depends on the application’s memory requirements, execution time and reliability.
| Requirement | Recommended Approach |
| Small temporary variables | Stack |
| Function parameters | Stack/registers, depending on calling convention |
| Fixed-size sensor buffers | Static allocation |
| Data with runtime-determined size | Heap or controlled memory pool |
| Time-critical operations | Prefer predictable allocation |
| Large data structures | Static storage or carefully managed memory region |
| RTOS task execution | Dedicated task stacks |
For most memory-constrained microcontroller applications, a combination of static allocation and stack memory provides a good starting point.
Heap allocation should be introduced only when its flexibility is genuinely required and its behavior can be controlled.
Stack and heap memory are essential concepts in microcontroller programming and Embedded C development.
Stack memory manages function execution, local variables and return information, while heap memory supports dynamic memory allocation during runtime.
Although both memory areas provide important functionality, they have different advantages and limitations. Stack memory generally provides faster and more predictable allocation, whereas heap memory offers flexibility but requires careful management.
Understanding stack overflow, heap fragmentation, memory leaks and RAM optimization techniques helps embedded developers create reliable firmware for STM32, ARM Cortex-M, ESP32 and other microcontroller platforms.
For engineering students and aspiring embedded developers, learning how memory works at the hardware and software levels is an important step toward becoming a skilled embedded firmware developer.
Stack memory is used for function execution, local variables and return information. Heap memory is used for dynamic memory allocation during program execution.
Stack memory is generally faster because allocation and deallocation are handled through simple stack operations. Heap allocation requires allocator management and can take variable amounts of time.
Frequent dynamic memory allocation can cause heap fragmentation, memory leaks and unpredictable execution time. These problems can affect the reliability of embedded applications.
Stack overflow can occur because of large local arrays, excessive recursion, deep function calls or insufficient stack allocation.
Heap fragmentation occurs when repeated memory allocation and deallocation create scattered free memory blocks, making it difficult to allocate larger continuous memory regions.
Yes. Both stack and heap typically use SRAM in microcontrollers. They occupy different portions of the available RAM.
Indian Institute of Embedded Systems – IIES