Processor Architecture & CPU Internalsfoundational
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 betweensetjmpandlongjmpshould bevolatileto guarantee they retain their last value after alongjmp.
What volatile is NOT:
- It is not atomicity. A
volatile32-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.
volatiledoes 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 — notvolatile. (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
volatileto the right thing:volatile uint32_t *pis a pointer to volatile data;uint32_t * volatile pis a volatile pointer. - Overusing
volatilehurts 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.
