Ground Embedded KO Subscribe

Notes / C Language

volatile, const, static

volatile blocks optimisation, const fixes a value, static changes scope and lifetime. The three keywords asked about most in embedded work.

GROUND EMBEDDED · NOTEC Language 10volatile, const, static 1volatile uint32_t *ODR 2 = (uint32_t*)0x40020014; 3*ODR |= (1u << 5); 4while (1) { 5 if (flag) toggle(); 6}

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 volatile together?
  • Is a static variable inside a function placed on the stack, the heap or in the data area?
FREE PDFGet the 3 free ebooksIf you want to keep reading this topic as one book: BLDC motor control, serial communication and sensors at book length. PDFs are in Korean.Get them free →