The Most Dangerous Keyword in C/C++: Why volatile Is Not Atomic

2026-11-15来源 dev.toEnglish

The Most Dangerous Keyword in C/C++: Why volatile Is Not Atomic

One of the most persistent myths in systems programming is the belief that declaring a shared variable as volatile makes it safe for concurrent multi-threaded access.

Consider the following multi-threaded counter:

volatile int counter = 0; void worker() { for (int i = 0; i < 100000; i++) { counter++; // DATA RACE & LOST UPDATES! } } 

Running two threads executing worker() concurrently will almost never result in 200,000. Increments will be silently lost.

To understand why, we must examine the difference between Compiler Optimizations and CPU Hardware Coherence.

1. What volatile Actually Does

The volatile keyword was introduced into the C standard in 1989 for a single specific purpose: interacting with Memory-Mapped I/O (MMIO) hardware registers and asynchronous signal handlers.

It communicates one instruction to the compiler:

"Do not cache this variable inside a CPU register. Do not optimize away repeated reads or writes. Every read must emit a fresh load from memory."

// Without volatile: while (!flag) {} // Compiler may optimize to: if (!flag) while(1){} // With volatile: while (!flag) {} // Compiler emits a CMP instruction reading RAM every loop iteration 

2. What volatile Fails to Provide

Failure 1: Zero Atomicity

The operation counter++ is not a single atomic hardware instruction. It decomposes into a 3-step sequence in assembly:

mov eax, dword ptr [counter] ; 1. Read from memory into register add eax, 1 ; 2. Increment register value mov dword ptr [counter], eax ; 3. Write back to memory 

If Thread 1 reads counter = 5, and is preempted before Step 3, Thread 2 can read 5, increment to 6, and write back. When Thread 1 resumes, it writes 6, completely obliterating Thread 2's increment.

Failure 2: Zero Memory Barriers

Modern superscalar CPUs (x86, ARM64) execute instructions out of order to maximize execution pipeline throughput.

volatile imposes constraints on the compiler, but it emits zero hardware memory barrier instructions (like MFENCE or DMB). The CPU is free to reorder reads and writes around volatile variables across cache lines.

3. The Modern Solution: std::atomic<T>

For thread-safe operations in C and C++, never use volatile. Use the standard atomic types:

#include <atomic> std::atomic<int> counter{0}; void worker() { for (int i = 0; i < 100000; i++) { counter.fetch_add(1, std::memory_order_relaxed); // Hardware Atomic! } } 

On x86, this compiles to the hardware instruction lock inc [counter], asserting the CPU bus lock and guaranteeing atomic execution.

Read the full systems engineering breakdown at Binary Dose.

原文:https://www.a8q9.cn/en/article/the-most-dangerous-keyword-in-c-c-why-volatile-is-not-atomic-48de94.html

相关文章