Need Help or More Information?
Questions about this free C programming course, embedded C training or career guidance? Get in touch.
Storage Classes in Embedded C: auto, register, static, extern and Where They Live
How the four storage class specifiers decide lifetime, linkage and memory placement, which sections (.text, .rodata, .data, .bss, stack) hold each variable on the STM32F407, and how the start-up code prepares them. Remember this whenever you study storage classes in embedded C.
What You Will Learn
nm and size output to see the placementconst data costs flash instead of SRAMKey Terms
1. What Is a Storage Class?
Every variable in C has a type (what the bits mean) and a storage class (where and for how long the bits are kept). Lesson 4.2 showed that scope and lifetime are separate ideas; the storage classes in embedded C are the keywords that choose between them and, on a microcontroller, choose which physical memory the variable occupies. Every one of the storage classes in embedded C comes down to this.
C has four storage class specifiers that you will meet in everyday code. Each one answers three questions: how long does the object live, which other files can see it, and what does it contain before you assign anything? This is why storage classes in embedded C matter on a small SRAM.
| Specifier | Typical placement | Lifetime | Linkage | Initial value if not given |
|---|---|---|---|---|
auto | Stack or CPU register | Block (one call) | None | Indeterminate (garbage) |
register | CPU register (hint) | Block (one call) | None | Indeterminate (garbage) |
static | .data / .bss in SRAM | Whole program | Internal at file scope, none in a block | Zero |
extern | Wherever it is defined | Whole program | External | Zero (at the definition) |
destroyed on exit
stack
asks for a CPU register
no & allowed
never destroyed
.data / .bss
defined elsewhere
no storage here
typedef and _Thread_local, which follow the same rule). You cannot write static extern int x;.2. auto: The Default for Locals
Inside a function every variable is auto unless you say otherwise. The keyword is almost never written, because writing int x; already means auto int x;. An auto variable is created when execution enters its block and disappears when the block ends. The compiler normally keeps it in a CPU register; if it needs more variables than there are registers, or you take its address, it spills the variable to the stack in SRAM.
Because the stack is reused by every call, an uninitialized auto variable contains whatever an earlier function left behind. On a Cortex-M4 the stack lives at the top of SRAM and grows downward, and it is shared by main(), every function it calls and every interrupt handler. A large automatic array is therefore a classic cause of stack overflow.
uint8_t buf[2048]; consumes 2 KB of stack on every call and again on every nested call. In an RTOS task with a 1 KB stack this overwrites the next task’s memory without any error message.3. register: A Hint That Modern Compilers Ignore
register asks the compiler to keep a variable in a CPU register for speed. Two things matter in practice. First, it comes with a rule: you cannot take the address of a register variable, because registers have no addresses. Second, modern compilers allocate registers far better than a programmer can, so with optimization on, register changes nothing except that rule.
On the STM32F407 the Cortex-M4 core has thirteen general purpose registers (R0–R12), so only the hottest few variables of a function ever fit. The compiler decides which. The only remaining legitimate use is in rare hand-tuned inner loops on very old compilers; otherwise leave it out. In C++17 the keyword was removed altogether.
register is a hint, the compiler may ignore it, and it forbids & on the variable.4. static: Program Lifetime in Two Flavours
Lesson 4.2 covered static in detail. The memory view adds one more fact: a static variable never lives on the stack. The linker gives it a fixed address in SRAM at build time. That has three consequences that matter in firmware:
- Its address is the same on every run and can be read from the
.mapfile. - It is never freed, so it permanently consumes SRAM whether or not its function ever runs.
- It starts at zero or at its initializer without any code in your function: the start-up code did the work before
main().
| Where written | Lifetime | Visibility | Example |
|---|---|---|---|
| Inside a function | Whole program | That function only | static uint8_t last; |
| File scope (variable) | Whole program | That file only (internal linkage) | static uint32_t s_ticks; |
| File scope (function) | n/a (code) | That file only | static void helper(void); |
5. extern: A Name Without Storage
extern does not allocate memory. It tells the compiler that an object with this name and type exists in another translation unit and the linker will connect them. Every global you share between files follows the pattern from Lesson 4.1: one definition in a .c file, an extern declaration in a header.
A function declared without a body behaves the same way: void led_toggle(uint8_t bit); is implicitly extern, which is why headers full of prototypes work.
6. The Memory Sections of a Firmware Image
When the linker builds your program it sorts everything into sections. A storage class decides which section a variable goes into, so the section names are essential vocabulary for the storage classes in embedded C:
| Section | Contains | Lives in | Initialised by |
|---|---|---|---|
.text | Machine code of functions | Flash | Programming the chip |
.rodata | const data, string literals, lookup tables | Flash | Programming the chip |
.data | Initialized globals and statics | SRAM (initial values stored in Flash) | Start-up code copies Flash → SRAM |
.bss | Uninitialized (zero) globals and statics | SRAM | Start-up code fills with zero |
| stack | auto variables, return addresses, saved registers | SRAM (top) | Nothing: garbage |
| heap | malloc memory | SRAM (between .bss and stack) | Nothing |
7. Proof: Where Each Variable Really Goes
Instead of trusting a table, ask the toolchain. The program below is compiled to an object file and inspected with nm, which lists every symbol with a letter for its section. (Upper case = visible to the linker, lower case = local to the file.)
g_initands_totalhave non-zero initializers, so they are in.data.g_zero,s_blankands_callsare zero, so they are in.bss: no initial value needs to be stored in flash.g_constandg_msgareconst, so they are read-only data.a, the automatic variable, does not appear at all: it is a register or a stack slot decided at run time.- Note the name
s_total.Nstyle suffix on static locals: the compiler invents a unique file-level name because the real name is only meaningful inside the function.
The section letters above come from a PC build. The classification is a rule of the C language, so an ARM build with arm-none-eabi-gcc sorts these same variables into the same sections, although the addresses differ. One ARM detail: with the default options, GCC for ARM may also place small const data in .rodata with extra names such as .rodata.str1.1 for string literals.
8. How Much Memory Does a Program Use? The size Command
Embedded builds end with a size report. The numbers map directly to the sections above and they answer the question “will this fit?”:
| Column | Meaning | Which memory is used |
|---|---|---|
text | Code plus read-only data (.text + .rodata) | Flash |
data | Initialized variables | Both: flash (initial image) and SRAM (working copy) |
bss | Zero-initialized variables | SRAM only |
Rule of thumb for an STM32F407 build: Flash used = text + data and static SRAM used = data + bss. The heap and the stack come on top, so you still need free SRAM for them. A linker error such as “region ‘RAM’ overflowed by N bytes” means data + bss (+ the heap and stack you reserved) exceeded the 128 KB.
9. What the Start-up Code Does Before main()
Power-up leaves SRAM full of random bits. Before your main() can rely on the guarantees of the language, the reset handler must make two memory regions match the C rules. On the STM32F407 it runs this sequence on every reset:
The start-up file generated for the STM32F407 uses symbols created by the linker script to find the regions (typically _sidata for the load address in flash, _sdata/_edata for .data in SRAM and _sbss/_ebss for .bss). A host-side model of the two loops makes the idea visible:
SRAM reserves 4 bytes for the working copy
Total: 4 B flash + 4 B SRAM
SRAM reserves 4000 bytes, cleared at reset
Total: 0 B flash + 4000 B SRAM
10. Storage Classes in Embedded C: Embedded Significance
On a PC, memory is plentiful and the operating system hides placement. On an STM32F407 you have 1 MB of flash and 128 KB of main SRAM (plus 64 KB of core-coupled memory), so the storage class of each variable is a design decision.
| Goal | What to do | Effect on memory |
|---|---|---|
| Keep a lookup table out of SRAM | static const uint16_t sine[256] = {...}; | Lives in flash (.rodata), 0 bytes of SRAM |
| Keep a file-private counter | static uint32_t s_ticks; | 4 bytes of .bss, hidden name |
| Share a flag with an ISR | extern volatile uint8_t g_flag; in a header | 1 byte of .bss, visible everywhere |
| Avoid stack overflow from a large buffer | Make it static in the function or a file-private global | Moves from stack to .bss, fixed size known at link time |
| Save flash from zero-filled arrays | Leave them uninitialized (.bss) instead of = {0} where the compiler would store explicit zeros | Usually the same, but check with size |
| Know the worst-case RAM | Read the linker map and size output | data + bss + heap + stack must fit |
The const trap
const on a global usually means flash, but const on a local only prevents assignment: the object is still on the stack. To keep a table in flash, make it a file-scope or static const object. Lesson 13 returns to this.
11. Common Mistakes With Storage Classes
auto variables are indeterminate. Only static-duration objects are zero-initialized.static or move it to file scope.uint16_t sine[256] = {...}; is copied into SRAM at start-up, using 512 bytes of RAM and 512 of flash. Add const..bss zeroing after a custom start-up change: Test it: a global that starts non-zero after reset proves the clear loop is missing.size reports only data + bss. A program at 120 KB of 128 KB of static data has almost no stack left.12. Best Practices for Storage Classes in Embedded C
- Use the storage class that gives the smallest lifetime and visibility that works: locals first, file
staticnext, true globals last. - Never write
auto; do not writeregister. - Mark constant tables
constat file scope so they stay in flash. - Keep big buffers out of the stack:
staticor file-scope, with a clear owner. - Declare shared variables
externin one header and define them in one source file that includes that header. - After every build, read the
sizereport and watch data + bss against the 128 KB limit. - Open the linker map file at least once per project to see the real addresses.
- Initialize automatic variables where you declare them.
Key Takeaways — Lesson 4.3
- The four storage classes in embedded C are
auto,register,staticandextern. autolocals live on the stack (or in registers) and are not initialized.registeris only a hint and forbids the address-of operator.staticobjects live at a fixed address in.dataor.bssfor the whole program.externcreates no storage; it refers to a definition elsewhere..datauses flash and SRAM;.bssuses SRAM only;constglobals use flash only.- The start-up code copies
.dataand clears.bssbeforemain(). - Tools:
nmshows the section of each symbol andsizetotals them.
Frequently Asked Questions
Ready to practise?
Next: nm and size on real programs, static versus automatic addresses, const in flash and a start-up copy demo.
Need Help or More Information?
Questions about this free C programming course, embedded C training or career guidance? Get in touch.

2 Comments