Precision Placement: Using GCC Section Attributes in Embedded C
Learn how to use GCC's __attribute__((section)) and linker scripts to precisely place code and data in specific memory regions to optimize RAM and Flash usage in embedded C.
23 Jul 2025, 03:10 UTC

A large calibration table intended for Flash memory ends up occupying precious RAM because the compiler placed it in a default read-only section that the startup code copies to RAM. On a resource-constrained MCU, this wastes memory and increases boot time. The solution is using GCC's __attribute__((section)) to tag specific objects or functions for a named linker section, allowing the linker script to map them to a precise physical memory address.
How the Section Attribute Works
The __attribute__((section("name"))) is a GCC extension that tells the compiler to associate a declaration with a specific ELF (Executable and Linkable Format) section rather than the standard .text, .data, or .bss sections. This attribute must follow the declaration.
For a data object:
static const uint16_t calib_table[256] __attribute__((section(".calib"))) = { 0x1234, 0x5678 };For a function:
void fast_isr(void) __attribute__((section(".fast_code")));It is important to understand that the attribute only tags the object. It does not dictate the final memory address; that responsibility belongs to the linker script. If the linker script does not explicitly handle the named section, the linker may place it in a default area or throw an error depending on the configuration.
Connecting Attributes to the Linker Script
To make the attribute effective, the linker script must define a MEMORY region and a corresponding SECTIONS entry. The SECTIONS entry collects all input sections with the matching name and assigns them to a hardware region.
MEMORY
{
FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 512K
RAM (rwx) : ORIGIN = 0x20000000, LENGTH = 128K
}
SECTIONS
{
.text : { *(.text*) } > FLASH
.rodata : { *(.rodata*) } > FLASH
/* Map the custom .calib section to Flash */
.calib : { KEEP(*(.calib)) } > FLASH
/* Map the custom .fast_code section to RAM for speed */
.fast_code : { *(.fast_code) } > RAM
}The KEEP() keyword is critical; it prevents the linker's garbage collection (--gc-sections) from removing the section if it isn't explicitly referenced elsewhere in the code.
Worked Example: Flash-Resident Calibration Data
Goal: Ensure a 512-byte calibration table stays in Flash to save RAM.
C Source Implementation:
/* Use a macro for portability and consistency */
#define FLASH_SECTION __attribute__((section(".calib"), aligned(4)))
static const uint16_t system_calib[256] FLASH_SECTION = { 0xAAAA, 0xBBBB };Verification Steps:
Run these commands on your build host. No target hardware permissions are required for static analysis.
- Compile: Run
gcc -Wall -Wextra -T linker.ld -o firmware.elf. Check for warnings regarding unknown attributes. - Inspect Sections: Run
objdump -h firmware.elf. Verify that a section named.calibexists and its size matches the expected 512 bytes. - Verify Symbols: Run
readelf -S firmware.elfto ensuresystem_calibis associated with the.calibsection. - Map File Check: Examine the
.mapfile (generated via-Wl,-Map=firmware.map). Confirm the address ofsystem_calibfalls within the0x08000000FLASH range.
Risk: Misaligned sections can cause hardware hard faults on architectures like ARM Cortex-M if a 32-bit access is attempted on a non-4-byte boundary. Always use aligned(4) for 32-bit data.
Trade-offs and Limitations
While powerful, custom sections introduce overhead. Excessive fragmentation of memory can degrade cache performance and make the linker script difficult to maintain. Every new section is a new point of failure if the script is not updated in tandem with the code.
Portability is a significant limitation. IAR and Keil use different pragma-based syntax for memory placement. To support multiple toolchains, wrap the attribute in a conditional macro:
#ifdef __GNUC__
#define PLACE_IN_FLASH __attribute__((section(".calib")))
#elif defined(__ICCARM__)
#pragma location = ".calib"
#define PLACE_IN_FLASH
#endifActionable Closing: Start by isolating a single large constant array. Move it to a named section, update your linker script, and verify the address using objdump. Once the workflow is verified, use a centralized header file for all section macros to avoid typos across the codebase.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.