What “volatile” does in C (and C++)
Tags: c, cpp
Published: 2021-09-09 03:42:08

---
title: What “volatile” does in C (and C++)
published: true
description: What "volatile" does in C (and C++).
tags: c, cpp
//cover_image: https://direct_url_to_image.jpg
---

## Introduction

Occasionally, I come across either a use or explanation of the `volatile` keyword in C (or C++).  Most often, that use or explanation is simply _wrong_.  So here, hopefully once and for all, is what `volatile` in C (and C++) actually (and only) does.

## Legitimate Uses of `volatile`

There are only three legitimate uses of `volatile` in C (and C++):

1. To tell the compiler that objects qualified with `volatile` _may_ either be modified or cause side effects in ways unknown to the compiler; therefore, _not_ to optimize away accesses to such objects _nor_ reorder their accesses with respect to other operations that have visible side effects.

2. For use in a signal-handler.

3. For use with `setjmp()`.

That’s it.  Any other use of `volatile` is innocuous, inefficient, or simply _wrong_.

## Use 1: Optimization Suppression

The C11 standard (§&nbsp;6.7.3 ¶&nbsp;7) has the following to say about the first use of `volatile`:

> An object that has volatile-qualified type may be modified in ways unknown to the implementation or have other unknown side effects.

This typically means memory-mapped I/O, that is particular memory addresses are mapped either to specialized hardware or functions, so that reading from such an address actually reads a current value from such hardware (say, some kind of sensor) or writing to such an address actually causes a change in such hardware (say, sends a value over an old-fashioned modem).

For example, consider this code that sends all the characters of a C string over a modem that is mapped to the address `0x1000`:

```c
char *const MODEM = (char*)0x1000;    // memory-mapped address

void modem_send_s( char const *s ) {
    while ( *s )
        *MODEM = *s++;
}
```

Writing a character to `*MODEM` sends that character. The problem is that a compiler _might_ be clever and optimize that code to be:

```c
void modem_send_s( char const *s ) {  // compiler optimized code
    while ( *s++ )
        ;
    *MODEM = s[-1];
}
```

Under ordinary circumstances, it would be within its rights to do so (because if `MODEM` were a pointer to ordinary memory, only the last write matters).  But in the memory-mapped I/O case, it would be wrong (because the compiler doesn’t know that address `0x1000` is special).

The C11 standard continues:

> Therefore any expression referring to such an object shall be evaluated strictly according to the rules of the abstract machine....

That means _no_ reads from _nor_ writes to such an object will be optimized away _nor_ instructions reordered by the compiler even if it normally would do so.

The way to tell the compiler _not_ to optimize accesses to an object is by qualifying it with `volatile`:

```c
char volatile *const MODEM = (char*)0x1000;
```

(Read from right-to-left: `MODEM` is a constant pointer to a volatile `char`; or use [cdecl](https://github.com/paul-j-lucas/cdecl).  Just as with `const`, `volatile` can be written to the right or “[east](http://slashslash.info/eastconst/)” of the base type.)

## Use 2: Signal Handling

The C11 standard (§&nbsp;7.14.1.1 ¶&nbsp;5) has the following to say about the second use of `volatile`: 

> If the signal occurs ..., the behavior is undefined if the signal handler refers to any object with static or thread storage duration that is not a lock-free atomic object other than by assigning a value to an object declared as `volatile sig_atomic_t` ....

This means that if you want to refer to any object outside of the signal handler function, the type of that object (other than lock-free atomics) _must_ be `volatile sig_atomic_t`.  For example:

```c
sig_atomic_t volatile last_sig_val;

void signal_handler( int sig_val ) {
    // ...
    last_sig_val = sig_val;
}
```

## Use 3: `setjmp()`

The C11 standard (§&nbsp;7.13.2.1 ¶&nbsp;3) has the following to say about the third use of `volatile`:

> ... the values of objects of automatic storage duration that are local to the function containing the invocation of the ... `setjmp` macro that do not have `volatile`-qualified type and have been changed between the `setjmp` invocation and `longjmp` call are indeterminate.

This means that if you modify a local variable between the time `setjmp()` is called and `longjmp()` returns, that variable _must_ be declared `volatile`. For example:

```c
void f() {
    volatile int count = 0;
    if ( setjmp( jmp_buf ) != 0 )
        g( ++count );
    // ...
}
```

That’s it: those are the only three legitimate uses of `volatile` in C (and C++).

For more about `setjmp()`, see [here](https://dev.to/pauljlucas/setjmp-longjmp-and-exception-handling-in-c-1h7h).

## A Digression on “Atomic”

Since the word “atomic” came up, it should be explained because it can have multiple meanings:

1. A value operation completes with no possible intervening operation by another thread, e.g., writing single-byte values on any CPU, or 16- or 32-bit values on a 32-bit CPU, or 64-bit values on a 64-bit CPU.
2. An updated value is visible to other CPUs.
3. Multiple value operations complete with no possible intervening operation by another thread, e.g., updating A and B together (“transactional”).

“Atomic” always means #1; it usually also means #2; or all three. In the case of `sig_atomic_t`, however, it means _only_ #1.

## `volatile` in Other Languages

I’ve been consistently mentioning `volatile` as it pertains to C (and C++) to emphasize that this explanation of `volatile` pertains _only_ to C (and C++) because the `volatile` keyword appears in other languages, for example Java and C#.

In those languages, `volatile` provides stronger guarantees regarding operations and therefore _can_ be used for limited forms of thread-safety.  And this can sometimes cause confusion as to what `volatile` means in C (and C++).

## Wrong Uses of `volatile`

In C (and C++), `volatile`:

+ Is _not_ a synonym for either `_Atomic` (in C) or `std::atomic<T>` (in C++).
+ Does _not_ use [memory barriers](https://en.wikipedia.org/wiki/Memory_barrier).
+ Therefore, does _not_ guarantee thread-safety.
+ Limits _only_ what optimizations the compiler may do.
+ Does _not_ limit what the hardware can do.

In particular, the hardware is still free to do things like memory caching, instruction parallelization, and speculative execution.

## Conclusion

`volatile` in C (and C++) is used only for memory-mapped I/O, signal handlers, or with `setjmp()`.  Since those uses are rarely needed, it’s entirely possible to go years or even one’s entire career without ever using `volatile` in a C (or C++) program.