HomeAbout UsContact Us

Linker Scripts and Memory Layout in Embedded C: A Practical Guide

By Jithin Tom
Published in Embedded C/C++
July 02, 2026
2 min read
Linker Scripts and Memory Layout in Embedded C: A Practical Guide

Table Of Contents

01
Why Linker Scripts Matter
02
Memory Regions Definition
03
Section Placement
04
Key Linker Script Concepts
05
Debugging Linker Issues
06
Summary
07
Related Reading
08
References
09
Frequently Asked Questions

When developing firmware for microcontrollers, understanding how your code and data are placed in memory is crucial for creating reliable embedded systems. The linker script is the bridge between your C/C++ source code and the physical memory layout of the target hardware.

Why Linker Scripts Matter

In embedded systems, you don’t have the luxury of an operating system managing memory layout for you. You must explicitly define where your program’s sections (.text, .data, .bss, stack, heap) reside in the available memory resources (Flash, RAM, etc.). A well-designed linker script ensures:

  • Deterministic placement: Code and variables end up exactly where you expect them
  • Memory protection: Different sections get appropriate read/write/execute permissions
  • Efficient resource utilization: No wasted memory space or overlaps
  • Hardware integration: Memory-mapped peripheral regions are accounted for, preventing code or data from being placed at I/O addresses

Memory Regions Definition

The foundation of any linker script is the MEMORY command, which defines the available memory resources on your microcontroller:

MEMORY
{
/* Flash memory for code and read-only data */
FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 512K
/* SRAM for initialized/uninitialized variables, stack, heap */
RAM (rwx) : ORIGIN = 0x20000000, LENGTH = 128K
/* Optional: External memory regions */
EXTMEM (rwx): ORIGIN = 0x60000000, LENGTH = 32M
}

Memory Layout Diagram

Flash Memory (0x08000000 - 0x0807FFFF)
+----------------+ 0x08000000
| Vector Table |
+----------------+
| .text | (Code + .rodata)
+----------------+
| .ARM.exidx | (Exception index)
+----------------+
| .data (load) | <-- LMA (Load Memory Address) of .data
+----------------+
RAM (0x20000000 - 0x2001FFFF)
+----------------+ 0x20000000
| .data (run) | <-- Copied from Flash at startup (VMA)
+----------------+
| .bss | <-- Zero-initialized at startup
+----------------+
| Heap | --> Grows upwards
+----------------+
| ... | (Free space)
+----------------+
| Stack | <-- Grows downwards
+----------------+ 0x20020000 (_estack / Initial SP)

The syntax is: NAME (attributes) : ORIGIN = address, LENGTH = size

Attributes:

  • r: readable
  • w: writable
  • x: executable
  • a: allocatable
  • i: initialized
  • l: same as i
  • !: negates preceding attributes

Section Placement

After defining memory regions, you tell the linker where to place each section from your object files:

/* Minimum sizes for heap and stack — override these from the build system if needed */
_Min_Heap_Size = 0x00000200; /* 512 bytes */
_Min_Stack_Size = 0x00000800; /* 2 KB */
/* Highest address of the user mode stack */
_estack = ORIGIN(RAM) + LENGTH(RAM); /* one past last byte of RAM */
SECTIONS
{
/* Vector table must be at the very start of Flash */
.isr_vector :
{
KEEP(*(.isr_vector))
. = ALIGN(4);
} > FLASH
/* Main program code and read-only constants */
.text :
{
. = ALIGN(4);
*(.text*) /* All code sections */
*(.glue_7) /* ARM-to-Thumb interworking glue */
*(.glue_7t) /* Thumb-to-ARM interworking glue */
*(.eh_frame) /* Exception handling frames */
*(.rodata*) /* Read-only data */
KEEP (*(.init))
KEEP (*(.fini))
. = ALIGN(4);
_etext = .; /* End of .text section */
} > FLASH
/* Exception handling index (for C++ or stack unwinding) */
.ARM.exidx :
{
__exidx_start = .;
*(.ARM.exidx*)
__exidx_end = .;
. = ALIGN(4);
} > FLASH
/* Used by the startup to initialize data */
_sidata = LOADADDR(.data);
/* Initialized data: stored in Flash, runtime copy to RAM */
.data :
{
. = ALIGN(4);
_sdata = .; /* Start address for initialization */
*(.data*) /* All initialized data */
. = ALIGN(4);
_edata = .; /* End address */
} > RAM AT> FLASH
/* Uninitialized data: zeroed at startup */
.bss :
{
. = ALIGN(4);
_sbss = .; /* Start of .bss */
__bss_start__ = _sbss;
*(.bss*) /* All uninitialized data */
*(COMMON) /* Common symbols */
. = ALIGN(4);
_ebss = .; /* End of .bss */
__bss_end__ = _ebss;
} > RAM
/* Verify enough RAM remains for heap and stack */
._user_heap_stack :
{
. = ALIGN(8);
PROVIDE ( end = . );
PROVIDE ( _end = . );
. = . + _Min_Heap_Size;
. = . + _Min_Stack_Size;
. = ALIGN(8);
} > RAM
}

Key Linker Script Concepts

  1. Start with vendor-provided scripts: Most silicon vendors provide baseline linker scripts for their devices - modify these rather than writing from scratch
  2. Keep it readable: Use comments liberally to explain why sections are placed where they are
  3. Verify with map files: Always examine the linker map file (.map) to confirm your sections ended up where expected
  4. Consider alignment requirements: Some peripherals or DMA controllers require specific address alignments
  5. Document memory usage: Clearly mark which regions are used for what purpose in your system
  6. Test boundary conditions: Try filling memory regions to 100% to ensure your linker script handles edge cases

Debugging Linker Issues

Common linker-related problems and their solutions:

Problem: “Section .text will not fit in region FLASH”
Solution:

  • Check actual code size with arm-none-eabi-size command
  • Verify FLASH length in MEMORY definition
  • Consider compiler optimization settings (-Os for size)

Problem: Variables not initialized correctly
Solution:

  • Verify .data section has AT> or AT() directive pointing to the Flash location
  • Check startup code actually performs data copy from Flash to RAM
  • Ensure _sidata symbol is correctly defined

Problem: Hard fault after startup
Solution:

  • Check stack placement and size
  • Verify vector table is correctly located and populated
  • Ensure memory permissions match section attributes (code=execute, data=read/write)

Summary

Linker scripts are a fundamental but often overlooked aspect of embedded development. Mastering them gives you precise control over your system’s memory layout, leading to more reliable and efficient firmware. While the syntax may seem cryptic at first, understanding the MEMORY and SECTIONS commands unlocks the ability to tailor your embedded system’s memory map to exactly match your hardware and application requirements.

Take time to study your microcontroller’s reference manual, experiment with different memory placements, and always verify results with the linker map file. Your future self (and anyone maintaining your code) will thank you for the clarity and precision a well-crafted linker script provides.

  • Startup Code and Vector Table in Cortex-M
  • Memory-Mapped IO and Peripheral Register Access in Embedded C
  • Static vs Dynamic Memory Allocation in Embedded Systems

References

  1. Arm Limited. “Arm® Compiler armlink User Guide.” Version 6.16, 2021.
  2. STMicroelectronics. “STM32F405xx/STM32F407xx Datasheet.” DocID022151 Rev 9, 2021.
  3. GNU Binutils. “LD - The GNU Linker.” Version 2.40, 2022.
  4. IAR Systems. “IAR C/C++ Compiler Reference Guide for ARM.” Version 9.20, 2021.
  5. Joseph Yiu. “The Definitive Guide to ARM® Cortex®-M3 and Cortex®-M4 Processors.” 3rd ed., Newnes, 2013.
  6. Elecia White. “Making Embedded Systems: Design Patterns for Great Software.” O’Reilly Media, 2011.

Frequently Asked Questions

What is a linker script and why is it important in embedded systems?

A linker script is a text file that describes how the linker should map input sections to output sections in the final binary. In embedded systems, it's crucial for placing code and data at specific memory addresses (like flash for code, RAM for variables) and defining memory regions with proper access permissions.

How do you define memory regions in a linker script for an embedded microcontroller?

Memory regions are defined using the MEMORY command, specifying name, origin (start address), and length. For example: MEMORY { FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 512K; RAM (rwx) : ORIGIN = 0x20000000, LENGTH = 128K; }

What are common sections placed in different memory regions by linker scripts?

Common sections include .text (code and read-only constants like .rodata) in flash, .data (initialized variables) in RAM (loaded from flash), .bss (uninitialized variables) in RAM (zeroed at startup), and .stack/.heap in RAM. Peripherals might be mapped to specific addresses too.

Tags

embedded-clinkermemorystartup

Share


Previous Article
Embedded Firmware Debugging Techniques: From Printf to JTAG
Jithin Tom

Jithin Tom

A Closer Look at C/C++, RTOS, and Embedded Systems

Related Posts

Understanding const and volatile Pointer Types in Embedded C
Understanding const and volatile Pointer Types in Embedded C
June 29, 2026
2 min
© 2026, All Rights Reserved.
Powered By Netlyft

Quick Links

Advertise with usAbout UsContact Us

Social Media