volatile, const, static
volatile blocks optimisation, const fixes a value, static changes scope and lifetime. The three keywords asked about most in embedded work.
volatile
The compiler assumes "nobody but my code changes this variable" and skips reads. For a hardware register, or a flag changed in an interrupt, that assumption is wrong.
volatile uint8_t rx_done = 0; // set to 1 in the ISR
while (!rx_done) { } // without volatile this can become an infinite loop
volatile only forces a read from memory every time. It does not guarantee atomicity, that is, reading a multi-byte value in one go.
const
A promise that the value will not change. A large lookup table marked const is placed in Flash instead of RAM, which saves RAM. On a function argument it also documents "this buffer is only read".
static
- On a variable or function outside any function: visible only within this file. A tool for hiding a module's internals.
- On a variable inside a function: the value survives after the function returns. Use it to count calls or remember the previous value.
What happens without volatile
This is the common pattern of raising a flag in an interrupt and waiting for it in the main loop.
uint8_t rx_done = 0; // no volatile
void USART1_IRQHandler(void) { rx_done = 1; }
int main(void) {
while (!rx_done) { } // with optimisation, never exits
process();
}
As the compiler sees it, nothing inside the loop changes rx_done. So it reads the value once and turns the loop into while (1). This is the classic cause of a bug that runs fine in a debug build (-O0) and hangs only in a release build (-O2). Declared as volatile uint8_t rx_done, it is read from memory every time.
volatile does not guarantee atomicity
volatile only means "read and write every time". If an interrupt changes the value while a 32-bit MCU is reading a 64-bit variable, or an 8-bit MCU is reading a 16-bit counter, you read a half-changed value. For such variables, briefly block interrupts while reading, or read twice and check that the two match.
Where const data is placed
const uint16_t sine_table[256] = { /* ... */ }; // in Flash, 0 bytes of RAM
uint16_t sine_copy[256] = { /* ... */ }; // Flash + 512 bytes of RAM
With const, the linker keeps the table only in Flash (.rodata). On an MCU with a few KB of RAM, leaving const off a lookup table costs that much RAM.
With pointers, the meaning depends on position.
| Declaration | What cannot change |
|---|---|
const uint8_t *p |
The value p points to |
uint8_t * const p |
p itself (the address) |
const uint8_t * const p |
Both |
Three uses of static
- Inside a function: the value is kept between calls. Suited to state only that function uses, such as a debounce counter.
- File-scope variable: not visible from other files. Use it to hide a module's internal state.
- File-scope function: becomes private to that file. Fewer name clashes, and easier for the compiler to inline.
Check yourself
- Why would a pointer to a hardware status register be declared
const volatiletogether? - Is a
staticvariable inside a function placed on the stack, the heap or in the data area?