Search topics...

Explain when you should use "volatile" in C.

0 upvotes
Practice with AISoon

volatile tells the compiler that a variable's value may change in ways the compiler cannot see, so it must not optimize away or reorder accesses to it: every read must actually load from memory, and every write must actually store to memory. It disables caching the value in a register and disables eliminating "redundant" accesses.

Use volatile when:

  • Memory-mapped I/O / hardware registers: Reading a status register or writing a control register has side effects and can change independently of program flow. Peripheral register pointers should be volatile (e.g., #define UART_SR (*(volatile uint32_t*)0x40011000)). Without it, the compiler may collapse repeated reads or drop "useless" writes.
  • Variables modified by an ISR and read by main code (or vice versa): The main loop polling a flag set inside an interrupt must see the updated value; otherwise the compiler may hoist the read out of the loop and spin forever.
  • Variables shared with another asynchronous context the compiler doesn't model — e.g., a DMA-updated status word, or memory updated by another core/agent that you poll.
  • setjmp/longjmp: Local variables that are changed between setjmp and longjmp should be volatile to guarantee they retain their last value after a longjmp.

What volatile is NOT:

  • It is not atomicity. A volatile 32-bit increment (x++) is still load-modify-store and can be torn or interrupted. For atomic updates use a real atomic type/operation, disable interrupts around the critical section, or use a lock.
  • It is not a memory barrier for multicore ordering. volatile does not impose the cross-CPU ordering/visibility guarantees needed for SMP synchronization. For thread-to-thread concurrency use _Atomic/stdatomic.h (C11), proper locks, or explicit barriers — not volatile. (This is the classic "volatile is not for multithreading" point.)
  • It does not guarantee ordering with respect to non-volatile accesses — only ordering among volatile accesses to each object is constrained.

Practical notes:

  • Apply volatile to the right thing: volatile uint32_t *p is a pointer to volatile data; uint32_t * volatile p is a volatile pointer.
  • Overusing volatile hurts performance by defeating legitimate optimizations, so use it precisely where the "value can change behind the compiler's back" or "the access itself has a side effect" condition holds.