How would one go about telling the compiler to put this lookup table in its own section in flash, and not in the RAM?
Generally, using
__attribute__((section ("sectionname")))
const type varname[] = {
values...,
};
Any decent compile/link ecosystem should by default put const data in flash unless explicitly told to do otherwise - as mentioned there are sometimes reasons to do otherwise, like harvard architectures that may have a speed penalty, or where flash and RAM have different access speeds, but these should be something that has to be explicitly requested.
For architectures with a single address space for Flash and RAM,
yes.
In the above snippet,
__attribute__((section ("sectionname"))) is only needed to put the variable
in its own section in Flash.
That is,
const type varname[] = { values..., };suffices to put the variable in the normal Flash data section (whose name varies a bit depending on architecture).
This works with standard/default linker scripts for ARM Cortex-M targets using both GCC and Clang, for example.
Similarly, on older AVRs,
__flash const type varname[] = { values..., };suffices to put the variable in the normal Flash data section in (
.progmem.data), and have instructions accessing it use the
LPM instruction.
With GCC, this works only in C; with Clang, in both C and C++. This is because GNU g++ does not support address spaces for C++, and is also the reason why the Arduino environment is so wonky. Also note that the external definition, for use in other compilation units, is "
extern __flash const type varname[size-if-known];", i.e. the
__flash qualifier is required; otherwise the other compilation units would use data memory instructions for access.
To repeat why "its own section" is so useful with GCC/binutils/Clang toolchains, is that if you use a properly aligned and sized structures, you can do
__attribute__((section ("sectionname"), used)) static const struct-type varname_is_irrelevant = { definition };in different compilation units, different source files, and the linker will provide you all of them (in a semi-random order) in a single array at runtime, via
extern const struct-type __start_sectionname[]; extern const struct-type __stop_sectionname[]; #define ELEMENTS ((size_t)(__stop_sectionname - __start_sectionname)) #define ELEMENT(index) (__start_sectionname[index])For older AVRs, I recommend using the alias trick; you do also need to use
extern __flash const ... above.
Also, knowing the section name lets you do all sorts of
objdump and
objcopy shenanigans. Even relocation records are based on sections; that is, relocation records are typically offsets to a section, not offsets to a symbol value. It is easy, almost trivial, to manipulate complete ELF sections as part of your build; manipulating individual symbols within sections containing other symbols is fraught with hidden dangers. I don't manipulate individual symbols (other than
adding them) outside the C compiler and linker script. I often do manipulate (the contents of) entire sections outside the C compiler and linker script, knowing that it also affects any symbols defined within it. (Which I often discard, by making them local via
static const.)