Atomics and the C Memory Model
This chapter covers C11 atomics and the memory model that defines how concurrent accesses are ordered.
Why This Matters
Atomics are the foundation of lock-free programming and the formal way to express synchronization. The memory model defines *which* operations are allowed to see *which* effects — without it, you cannot reason about concurrent code.
Prerequisites
c.conc.2— data races and happens-before.
Core Concept
An atomic operation is indivisible: no observer can see an intermediate state. C11 provides _Atomic types and functions in <stdatomic.h>.
#include <stdatomic.h>
atomic_int counter = 0;
atomic_fetch_add(&counter, 1); /* atomic increment */
int v = atomic_load(&counter); /* atomic read */
Atomic operations take an optional memory order argument (c.conc.6). The default is memory_order_seq_cst.
The C memory model
The model defines a happens-before relation that orders effects:
- A program is free of data races only if every conflicting access is ordered
by happens-before.
- Atomics and mutexes establish happens-before edges between threads.
- Without such an edge, the compiler/CPU may reorder operations.
Examples
Atomic flag (spinlock-style)
#include <stdatomic.h>
static atomic_flag busy = ATOMIC_FLAG_INIT;
void acquire(void)
{
while (atomic_flag_test_and_set_explicit(&busy, memory_order_acquire))
; /* spin */
}
void release(void)
{
atomic_flag_clear_explicit(&busy, memory_order_release);
}
Atomic counter
#include <stdatomic.h>
static atomic_int counter = 0;
int next(void)
{
return atomic_fetch_add(&counter, 1);
}
How It Works
Atomics compile to hardware atomic instructions (lock xadd on x86, ldxr/stxr on ARM) with appropriate barriers. The memory model constrains compiler and CPU reordering so that a release in one thread synchronizes-with an acquire in another, making prior writes visible.
Variations
Lock-free vs. atomic
atomic_is_lock_free tells whether a given atomic type is truly lock-free on the target (some types may use a hidden lock). Use it when lock-freedom is a hard requirement.
Signal handlers
sig_atomic_t is a separate, signal-safe type, distinct from _Atomic.
Common Mistakes
- Assuming
_Atomicmakes a multi-step sequence atomic (it does not). - Using
volatileinstead of_Atomicfor shared data. - Forgetting that relaxed atomics do not order surrounding non-atomic data.
Undefined Behavior
- A data race on a non-atomic object is UB.
VERIFIED - Using an atomic with an invalid memory-order argument is UB.
Portability
- Atomics are C11 standard but optional (check
__STDC_NO_ATOMICS__). - MSVC has different atomics; GCC/Clang support C11 atomics.
Under the Hood
The compiler emits fences/barriers as needed by the memory order. The CPU's memory model (e.g., x86's strong TSO vs. ARM's weak model) determines which barriers are necessary.
Practical Usage
- Use atomics for counters, flags, and simple lock-free structures.
- Use mutexes for larger critical sections.
- Pick the weakest memory order that is correct (
c.conc.6).
Exercises
1. Implement an atomic counter and run it from multiple threads. 2. Use atomic_flag to build a spinlock. 3. Check atomic_is_lock_free for several types. 4. Explain why _Atomic is not the same as volatile.
Deep Challenge
Explain the difference between an atomic RMW (read-modify-write) and a sequence of atomic load then store, and why only the RMW is safe for a lock-free counter. Show the interleaving that breaks the load/store version.
Related Concepts
c.conc.6— memory orders.c.conc.8— lock-free programming.c.stdlib.12— stdatomic.h.
References
- ISO C §7.17 (stdatomic.h), §5.1.2.4 (data races and memory model).
Verification
- Atomic operations are indivisible; memory model is C11.
VERIFIED - Data race on non-atomic is UB.
VERIFIED - No example was executed during generation unless noted. `Execution not
verified.`
Progress
- [ ] Read
- [ ] Understand
- [ ] Complete examples
- [ ] Complete exercises
- [ ] Complete deep challenge
Concept checkboxes
- [ ] _Atomic types
- [ ] atomic load/store/fetch
- [ ] atomic_flag
- [ ] Happens-before
Mastery levels
| Concept | Current level (0–8) | Target level |
|---|---|---|
| c.conc.atomic | 0 | 6 |