Why Hardware Registers Must Be volatile

The hardware can change values.
The compiler assumes it cannot.
volatile forces reality back into the code.

1. The Mismatch

In C and C++, the compiler assumes that memory behaves predictably.

  • A value does not change unless the program modifies it
  • Repeated reads return the same result
  • Unused reads or writes can be removed

These assumptions are valid for normal memory.

They are wrong for hardware registers.

2. Hardware Does Not Follow Software Rules

Hardware registers can change independently of the CPU.

For example:
  • a status register may change when a peripheral completes an operation
  • an interrupt flag may be set by hardware
  • a timer register changes continuously

From software’s perspective, the value changes without any visible write.

3. What Goes Wrong Without volatile

If a hardware register is not marked as volatile, the compiler may optimize in ways that break the system:

  • Caching values in registers → software never sees updated hardware state
  • Removing repeated reads → polling loops stop working
  • Reordering accesses → hardware sequence breaks

The program may look correct in source code, but the generated machine code behaves incorrectly.

4. What volatile Actually Does

The volatile qualifier tells the compiler:

  • every read must access memory
  • every write must be performed
  • no assumptions about value stability

This forces the compiler to generate instructions that match the exact intent of the code.

It does not make code faster or slower — it makes it correct when dealing with hardware.

5. Where It Is Required

volatile is essential when working with:

  • memory-mapped hardware registers
  • interrupt-shared variables
  • status flags modified by peripherals
  • communication with external hardware

Without it, firmware may silently fail even though the logic appears correct.

6. What volatile Does NOT Do

A common misunderstanding is that volatile provides synchronization or safety.

It does not:
  • prevent race conditions
  • guarantee atomic operations
  • replace proper concurrency handling

It only ensures that memory access happens as written.
The compiler trusts your code.
Hardware does not follow those rules.
volatile forces the compiler to respect reality.