Question
In C, why is the volatile keyword needed? What is it used for, and what effect does it have on a program?
For example, in what situations should a variable be declared as volatile, and what changes when the compiler sees that qualifier?
Short Answer
By the end of this page, you will understand what volatile means in C, why it exists, and when it should be used. You will also learn what volatile does not guarantee, how it affects compiler optimizations, and how it is commonly used with hardware registers, signal handlers, and shared state changed outside normal program flow.
Concept
volatile in C tells the compiler that the value of an object may change in ways the compiler cannot predict.
Normally, the compiler is allowed to optimize code by assuming that if your program did not write to a variable, then its value has not changed. That assumption is often correct for ordinary variables.
However, some values can change outside the current code path, such as:
- memory-mapped hardware registers
- variables modified by a signal handler
- values updated by external devices or special runtime environments
When an object is declared volatile, the compiler must be more careful:
- it should perform actual reads from memory when the program asks for the value
- it should perform actual writes to memory when the program assigns to it
- it should avoid certain optimizations that would remove, combine, or cache those accesses
Why this matters
Without volatile, the compiler may:
- keep a value in a register instead of re-reading memory
- remove a loop that appears to wait for a value to change
- combine multiple reads or writes into fewer operations
That can break programs that interact with hardware or asynchronous events.
What volatile does not do
A very important point: volatile is not a threading or synchronization tool.
It does not guarantee:
- atomic reads or writes
- thread safety
- mutual exclusion
- memory ordering between threads in the modern concurrency sense
For multithreaded communication, C uses atomics from or platform-specific synchronization tools such as mutexes.
Mental Model
Think of a normal variable like a note on your desk.
If you look at it once, you might keep the value in your head and stop checking the paper. That is like the compiler caching a value in a register.
A volatile variable is like a display board that someone else may update at any time.
Because the value may change unexpectedly, you must look at the board each time you need the latest value. You cannot safely assume the old value is still correct.
That is what volatile tells the compiler: do not assume this value stays the same just because this code did not change it.
Syntax and Examples
Basic syntax
volatile int flag;
This means flag is an integer whose value may change unexpectedly.
Example: waiting for a hardware or external update
volatile int ready = 0;
void wait_until_ready(void) {
while (ready == 0) {
/* wait */
}
}
Why volatile matters here
If ready were not volatile, the compiler might read it once, decide it is still 0, and turn the loop into something that never checks memory again.
With volatile, the compiler must re-read ready each time through the loop.
Example: memory-mapped hardware register
#define STATUS_REG (*(volatile unsigned int *)0x40000000)
{
((STATUS_REG & ) == ) {
}
}
Step by Step Execution
Consider this code:
volatile int ready = 0;
void wait_until_ready(void) {
while (ready == 0) {
}
}
Now walk through it:
readyis declared asvolatile int.- The compiler sees that
readymay change unexpectedly. wait_until_ready()starts running.- The loop checks
ready == 0. - Because
readyisvolatile, the compiler performs a real memory read. - If the value is still
0, the loop repeats. - On the next iteration, the compiler must read
readyagain. - If some external event changes
readyto1, a later loop iteration sees the new value. - The condition becomes false, and the function exits the loop.
What could happen without volatile
Real World Use Cases
1. Memory-mapped device registers
Embedded systems often access hardware by reading and writing fixed memory addresses.
#define UART_STATUS (*(volatile unsigned int *)0x40001000)
#define UART_DATA (*(volatile unsigned int *)0x40001004)
These values can change because of the device, not because of ordinary C code.
2. Polling for hardware state changes
A program may wait until a peripheral finishes work:
while ((UART_STATUS & 0x01u) == 0u) {
}
Without volatile, the compiler might optimize the repeated reads incorrectly.
3. Signal handlers
A signal can interrupt execution and modify a flag that the main code checks.
volatile sig_atomic_t interrupted = 0;
4. Special runtime or low-level systems code
Operating systems, firmware, kernels, and bootloaders often work with registers or memory locations that can change externally.
5. Debug or instrumentation hooks
In rare low-level cases, developers use volatile to force specific reads and writes to happen because the value may be observed outside normal program assumptions.
Real Codebase Usage
In real codebases, volatile is usually seen in low-level code, not ordinary business logic.
Common patterns
Hardware register definitions
#define REG32(addr) (*(volatile unsigned int *)(addr))
This pattern appears in embedded code to make register accesses explicit.
Polling loops with timeout protection
int wait_ready_with_timeout(void) {
int count = 1000000;
while ((status_reg & 1u) == 0u) {
if (--count == 0) {
return -1;
}
}
return 0;
}
Developers often combine volatile reads with a timeout so the program does not get stuck forever.
Signal-safe flags
volatile sig_atomic_t stop_requested = 0;
This is a common way to let a signal handler notify the main loop.
Common Mistakes
Mistake 1: Using volatile for thread safety
Broken idea:
volatile int counter = 0;
/* Thread A */
counter++;
/* Thread B */
counter++;
Why this is wrong:
counter++is not atomic- both threads can interfere with each other
- updates can be lost
Use atomics or a mutex instead.
Mistake 2: Thinking volatile prevents all optimization
volatile only restricts optimization of accesses to that specific object. It does not disable optimization for the whole function or program.
Mistake 3: Forgetting it for hardware registers
Broken example:
#define STATUS_REG (*(unsigned int *)0x40000000)
while ((STATUS_REG & 1u) == 0u) {
}
This may be optimized in unsafe ways because the compiler sees an ordinary pointer dereference.
Correct version:
# STATUS_REG (*(volatile unsigned int *)0x40000000)
Comparisons
| Concept | What it means | Good for | Not enough for |
|---|---|---|---|
volatile | Value may change unexpectedly; force actual accesses | Hardware registers, signal flags, low-level polling | Thread safety, atomicity |
const | Do not modify through this name | Read-only values, APIs, safety | External changes, synchronization |
_Atomic / <stdatomic.h> | Atomic operations and memory ordering | Multithreaded shared data | Direct replacement for hardware register semantics in every case |
| ordinary variable | Normal compiler assumptions apply | Regular program state | External asynchronous changes |
volatile vs
Cheat Sheet
Quick definition
volatile tells the C compiler that an object's value may change unexpectedly, so accesses must not be optimized away or cached as if the value were stable.
Syntax
volatile int x;
int volatile x;
These mean the same thing.
Typical uses
- memory-mapped hardware registers
- variables changed by signal handlers
- low-level polling of external state
Not for
- thread synchronization
- atomic increment/decrement
- locking
- replacing mutexes or atomics
Common examples
volatile sig_atomic_t stop;
#define REG (*(volatile unsigned int *)0x40000000)
Key rule
Use volatile when the value can change for reasons the compiler cannot see in normal code flow.
Related qualifiers
const: cannot be modified through that name
FAQ
What does volatile do in C?
It tells the compiler that a variable may change unexpectedly, so reads and writes to it should happen as written instead of being optimized away.
Why is volatile used for hardware registers?
Because hardware can change register values independently of normal C code. The compiler must not assume the value stays the same.
Is volatile needed for multithreading?
Usually no. volatile does not provide atomicity or proper synchronization between threads. Use atomics or locks instead.
Does volatile make code slower?
It can, because the compiler has fewer optimization options for that object. That is why it should be used only where needed.
Can a variable be both const and volatile?
Yes. That often represents a read-only value from the program's point of view that may still change externally, such as a status register.
Does volatile guarantee fresh values across CPU cores?
No. It is not a full memory-ordering or synchronization mechanism for concurrent programs.
When should I use volatile sig_atomic_t?
Use it for a flag shared between normal code and a signal handler, which is a classic C pattern.
Mini Project
Description
Build a small C program that simulates waiting for an external event using a signal. This demonstrates why a value that changes outside normal program flow should be declared carefully. It is a practical way to see the classic volatile sig_atomic_t pattern in action.
Goal
Create a program that runs in a loop until the user sends Ctrl+C, then exits cleanly using a flag set by a signal handler.
Requirements
- Declare a stop flag that can be safely checked in normal code and set in a signal handler.
- Register a handler for
SIGINT. - Keep looping until the stop flag changes.
- Print a message when the program starts and when it exits.
Keep learning
Related questions
Building More Fault-Tolerant Embedded C++ Applications for Radiation-Prone ARM Systems
Learn practical C++ and compile-time techniques to reduce soft-error damage in embedded ARM systems exposed to radiation.
C printf Format Specifier for bool: How to Print Boolean Values
Learn how to print bool values in C with printf, why no %b/%B specifier exists, and the common patterns to print true/false or 0/1.
Calling C or C++ from Python: Building Python Bindings
Learn the quickest ways to call C or C++ from Python, including ctypes, C extensions, Cython, and binding tools with practical examples.