ESP-IDF Memory Attributes: Should IRAM_ATTR and DRAM_ATTR Go in the Header or Source File?
Understanding Memory Placement in ESP-IDF
When developing firmware on Espressif chips (ESP32, ESP32-S3, ESP32-C3, etc.) using the ESP-IDF framework, managing memory placement is critical. Linker attributes like IRAM_ATTR, DRAM_ATTR, and RTC_DATA_ATTR instruct the compiler and linker to place code or data into fast internal RAM rather than slower SPI flash cache. This is essential for Interrupt Service Routines (ISRs) and time-critical functions that must run when flash cache is disabled.
However, official documentation frequently demonstrates functions as single static blocks. What happens when you split your code cleanly into header (.h) declarations and source (.c) definitions? Should you put IRAM_ATTR in the declaration or the definition?
The Short Answer
Place linker attributes like IRAM_ATTR and DRAM_ATTR on the definition in the source file (.c), not on the declaration in the header (.h).
While GCC allows attributes on declarations in some scenarios, putting them on the definition is the standard, least error-prone, and architecturally correct approach.
Why the Definition Is the Right Place
To understand why linker attributes belong in the .c file, it helps to examine what these macros actually do under the hood.
1. They Expand to GCC Section Attributes
In ESP-IDF, macros like IRAM_ATTR expand into GCC compiler attributes:
#define IRAM_ATTR __attribute__((section(".iram1.text")))
#define DRAM_ATTR __attribute__((section(".dram1.data")))The __attribute__((section(...))) directive instructs the compiler to emit the compiled machine code or allocated data into a specific ELF binary section when creating the object file (.o).
2. Callers Do Not Need Section Information
When external source files include your header to call a function or reference a variable, they only need to know the symbol name and its signature (return type, parameters, and variable type). The caller generates a standard function call or memory access instruction. It is the linker's job to resolve the final address in IRAM or DRAM when linking object files together. Therefore, exposing section details in the header file provides no benefit to external callers.
3. Avoiding Redundancy and Attribute Conflicts
Placing memory attributes in both the header and the source file can cause subtle build failures. If macro expansions differ or if the compiler detects mismatched or repeated attributes across translation units, it may emit compilation warnings or throw conflicting attribute errors.
Recommended Code Structure
Here is the clean, idiomatic way to separate declarations and definitions using ESP-IDF attributes:
Header File (my_module.h)
Keep your header file pure and portable by omitting target-specific section attributes:
#pragma once
#include <stdint.h>
// Public variable declaration
extern uint8_t my_dram_int;
// Public function declaration (e.g., an ISR or time-critical task)
int32_t my_iram_func(int32_t bar);Source File (my_module.c)
Include the ESP-IDF memory attribute headers and annotate the actual definitions:
#include "esp_attr.h"
#include "my_module.h"
// Place variable into internal Data RAM
DRAM_ATTR uint8_t my_dram_int = 42;
// Place function into internal Instruction RAM
int32_t IRAM_ATTR my_iram_func(int32_t bar) {
return bar + 42;
}Summary of Best Practices
- Always place
IRAM_ATTR,DRAM_ATTR, andRTC_DATA_ATTRon the definition in your.cimplementation files. - Keep
.hheader files free of section attributes to maintain clean interfaces and prevent linker/compiler mismatch errors. - Do not apply the attribute to both declaration and definition, as macro expansions can trigger compiler mismatches.
- Ensure you include
#include "esp_attr.h"in your source files to make these attribute macros available.