Are C Reads and Writes of an int Atomic?
Master System Design with Codemia
Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.
Introduction
In C++, plain reads and writes of an int are not something you should treat as safely atomic for multithreaded code. A particular machine may happen to perform aligned integer loads and stores atomically at the hardware level, but the C++ memory model still says that unsynchronized concurrent access to a non-atomic variable is a data race and therefore undefined behavior.
What "Atomic" Means in Practice
An atomic operation is one that appears indivisible to other threads. No thread can observe a torn or partially updated value, and the language defines how that operation interacts with other memory operations.
That second point matters. Even if your hardware performs a read or write in one machine instruction, the language-level synchronization guarantees still do not exist unless you use atomic types or other proper synchronization tools.
Why Plain int Is Not Enough
This code has a data race:
Even though int is a simple built-in type, this is still undefined behavior because one thread writes while another reads with no synchronization.
The problem is not just torn reads. The problem is that the C++ language does not define the interaction safely.
Use std::atomic<int> Instead
If you need concurrent reads and writes to an integer, use an atomic type.
Now the read and write are defined as atomic operations. Whether you want relaxed, acquire, release, or stronger ordering depends on the rest of the program, but the fundamental data-race problem is gone.
Hardware Reality Versus Language Guarantee
On many platforms, an aligned 32-bit int read or write is physically atomic. That fact often leads to misleading advice such as "plain int is fine on modern CPUs."
The problem with that advice is that portable C++ code must follow the C++ memory model, not rely on lucky hardware behavior. Compilers are allowed to optimize aggressively around non-atomic shared variables, and those optimizations are one reason undefined behavior matters here.
So the right answer is:
- hardware may make it look atomic sometimes
- C++ still does not let you rely on that in racy code
Atomic Read and Write Are Not the Same as Atomic Update
Even if individual loads and stores are atomic, compound operations are not automatically atomic.
This works because counter is atomic. But with a plain int, ++counter is definitely not safe across threads because it is a read-modify-write sequence, not one indivisible action.
That distinction matters a lot when people casually ask whether integer access is atomic. The practical code usually needs more than a single standalone load or store.
Common Pitfalls
One common mistake is equating "works on my machine" with "defined by the language." Concurrency bugs often pass tests until an optimization level, compiler, or architecture changes.
Another is assuming a simple built-in type is safe just because it fits in one register. C++ cares about synchronization semantics, not only hardware width.
Developers also sometimes use volatile for thread safety. In C++, volatile is not a substitute for std::atomic and does not fix data races.
Finally, do not forget that atomicity and ordering are different concerns. A variable can be atomic but still use the wrong memory order for the higher-level protocol.
Summary
- Plain
intaccess is not safely atomic in the sense required by the C++ memory model. - Unsynchronized concurrent reads and writes on a non-atomic
intare a data race and undefined behavior. - Hardware behavior does not replace language-level guarantees.
- Use
std::atomic<int>for shared integer state accessed across threads. - Compound operations such as increment need atomic types or other synchronization too.

