In this lesson, you will learn how to define the size and placement of the stack and heap in your linker script to ensure your application has enough memory without crashing.
In a bare-metal environment, the linker script is your blueprint for memory. While global variables are explicitly placed in sections like .data or .bss, the stack (for local variables and function calls) and the heap (for malloc) are often dynamic regions that exist within your RAM. If these regions aren't carefully managed, they can grow into each other—or into your global data—causing silent memory corruption.
Defining Boundaries in the Linker Script
We define these boundaries using symbols in the linker script. Usually, the stack grows downwards from the top of the RAM, while the heap grows upwards from the end of the uninitialized data section.
/* Define RAM boundaries */
_ram_start = 0x20000000;
_ram_end = 0x2001FFFF;
/* Define sizes */
_stack_size = 0x2000; /* 8KB */
_heap_size = 0x1000; /* 4KB */
SECTIONS {
.heap : {
_sheap = .;
. = . + _heap_size;
_eheap = .;
} > RAM
.stack : {
. = ALIGN(8);
_stack_bottom = .;
. = . + _stack_size;
_stack_top = .;
} > RAM
}
Preventing Overflow
The code above reserves specific chunks of RAM. The danger occurs when the application uses more memory than allocated. If the stack pointer exceeds _stack_bottom, it overwrites the heap or data sections.
To catch these issues during development, you can use a "stack guard." By placing the stack at the very end of RAM, any overflow will cause a hardware fault (Memory Management Fault) rather than silently corrupting your data, because the CPU will attempt to access an address outside the defined physical memory region. When setting these boundaries, always leave a safety margin; if your application performs deep recursion or heavy dynamic allocation, you must increase these values or risk a system hang.
Key Takeaway: By explicitly defining stack and heap sizes in the linker script, you create predictable memory boundaries that allow the CPU to trigger hardware faults if memory limits are exceeded, preventing silent data corruption.