Memory Models & Atomics

why volatile is not an atomicity tool

/ volatile -> VOL-uh-tuhl /

The volatile keyword is one of the most misunderstood words in C and C++. A widespread folk belief holds that marking a shared variable volatile makes it thread-safe or atomic. It does not. volatile and atomics solve different problems, and confusing them is a recipe for races that hide on x86 and surface on ARM.

What volatile actually means is narrow: it tells the COMPILER that this location may change in ways it cannot see (a memory-mapped hardware register, a variable modified by a signal handler in the same thread), so the compiler must not cache it in a register and must perform each read and write exactly as written, in source order relative to OTHER volatile accesses. That is its entire job. It says nothing about atomicity: a volatile 64-bit write on a 32-bit machine can still tear into two halves. It says nothing about cross-thread ordering: volatile imposes NO acquire/release semantics and emits NO CPU memory barrier, so the hardware may freely reorder a volatile access relative to ordinary memory, and another core may see stale data. And in C/C++ a data race on a volatile variable is STILL a data race, hence still undefined behavior.

The correct tools draw a clean line. For talking to hardware or a signal handler within ONE thread, use volatile (or in C11, volatile sig_atomic_t). For sharing data BETWEEN threads, use _Atomic / std::atomic, which guarantee atomicity AND let you choose a memory_order. (Java and C# muddied the water by giving their volatile acquire/release semantics — but C, C++, and Rust volatile do NOT, and Rust does not even have a volatile keyword, only read_volatile/write_volatile intrinsics for memory-mapped I/O.) Rule: volatile is for memory you do not fully control; atomic is for memory other threads share.

volatile int counter; counter++; /* NOT atomic: still load, add, store — two threads can lose an increment */ /* correct: */ atomic_int counter; atomic_fetch_add(&counter, 1); /* genuinely atomic */

volatile stops the compiler caching the value, but counter++ is still three separable steps — only an atomic RMW is safe.

In C/C++/Rust, volatile is NOT atomic, NOT ordered across threads, and NOT a substitute for synchronization — its sole valid uses are memory-mapped I/O and same-thread signal handlers; do not confuse it with Java/C# volatile, which is a different thing.

Also called
the volatile mythvolatile 迷思