1. Abstract
We propose deprecating most of 
The proposed deprecation preserves the useful parts of 
2. A Syntax of Three Parts
C and C++ have syntax for
, and it is a syntax in three parts.volatile The most obvious part is the abstract machine syntax, made by loads and stores present in the original program. If there is an expression that would have touched
memory in the original source, it will generate instructions by which each byte will be touched exactly once. If there had been shared memory, a signal, evenvolatile /setjmp , thelongjmp would have filled the compiler with doubt, the slowness and preciseness one expects from a compiler during external modifications. If it had been part of the memory model… but no, of course, it isn’t part of the memory model. In fact there are none of these things, and so the syntax remains.volatile Inside C, pairs of operations can huddle with
,++ , or-- . They’re used with quiet determination, avoiding serious code. In doing this withop = C adds a small, sullen syntax to the larger, hollow one. It makes an alloy of sorts, a counterpoint.volatile The third syntax is not an easy thing to notice. If you read the Standard for hours, you might begin to notice it in the Standard Library under its specializations and in the rough, splintered applications of design guidelines. It adds weight to Generic Programs which hold the instantiations of templates long specialized. It is in the slow back and forth of code reviews rubbing out esoteric corner cases. And it is all in C++, adding to classes that already are qualified through
.const The C++ Committee can move with the subtle certainty that comes from knowing many things.
is ours, just as the third syntax is ours. This is appropriate, as it is the most onerous syntax of the three, wrapping the others inside itself. It is as deep and wide asvolatile -qualification. It is heavy as a great river-smooth stone. It is the patient, cut-flower syntax of a feature which is waiting to be deprecated.const — The Name of
in the style of [NotW]volatile 
3. The Wise Programmer’s Fear
There are three things all wise programmers fear: C’s corner cases, a hardware platform with no documentation, and the anger of an optimizing compiler.
— The Name of
in the style of [NotW]volatile 
3.1. Overview
3.2. Proposed changes
This proposal has the following goals:
- 
     Continue supporting the time-honored usage of volatile setjmp longjmp 
- 
     Deprecate (and eventually remove) volatile op = -- ++ 
- 
     Deprecate (and eventually remove) volatile volatile 
- 
     Deprecate (and eventually remove) partial template specializations involving volatile volatile atomic numeric_limits 
- 
     Deprecate (and eventually remove) volatile atomic load store is_always_lock_free true. Preserve mostvolatile 
- 
     Deprecate (and eventually remove) non-reference and non-pointer volatile const volatile volatile 
A rationale for each of these is provided in §3.6 Why the proposed changes?.
3.3. When is volatile 
   Knowing your own ignorance is the first step to enlightenment.
― The Wise Man’s Fear [WMF]
Colloquially, 
Importantly, 
The order of 
That being said, 
3.4. How we got here
When discussing 
3.4.1. Original intent for volatile 
   [SEBOR] lays out the original intent for 
The use case that motivated the introduction of the
keyword into C was a variant of the following snippet copied from early UNIX sources [SysIII]:volatile #define KL 0177560 struct { char lobyte , hibyte ; }; struct { int ks , kb , ps , pb ; }; getchar () { register rc ; ... while ( KL -> ks . lobyte >= 0 ); rc = KL -> kb & 0177 ; ... return rc ; } The desired effect of the while loop in the
function is to iterate until the most significant (sign) bit of the keyboard status register mapped to an address in memory represented by thegetchar () macro (the address of the memory-mappedKL I/O register on the PDP-11) has become non-zero, indicating that a key has been pressed, and then return the character value extracted from the low 7 bits corresponding to the pressed key. In order for the function to behave as expected, the compiler must emit an instruction to read a value from the I/O register on each iteration of the loop. In particular, the compiler must avoid caching the read value in a CPU register and substituting it in subsequent accesses.KBD_STAT On the other hand, in situations where the memory location doesn’t correspond to a special memory-mapped register, it’s more efficient to avoid reading the value from memory if it happens to already have been read into a CPU register, and instead use the value cached in the CPU register.
The problem is that without some sort of notation (in K&R C there was none) there would be no way for a compiler to distinguish between these two cases. The following paragraph quoted from The C Programming Language, Second Edition [KR], by Kernighan and Ritchie, explains the solution that was introduced into standard C to deal with this problem: the
keyword.volatile The purpose of
is to force an implementation to suppress optimization that could otherwise occur. For example, for a machine with memory-mapped input/output, a pointer to a device register might be declared as a pointer tovolatile , in order to prevent the compiler from removing apparently redundant references through the pointer.volatile Using the
keyword, it should then be possible to rewrite the loop in the snippet above as follows:volatile while ( * ( volatile int * ) & KL -> ks . lobyte >= 0 ); or equivalently:
volatile int * lobyte = & KL -> ks . lobyte ; while ( * lobyte >= 0 ); and prevent the compiler from caching the value of the keyboard status register, thus guaranteeing that the register will be read once in each iteration.
The difference between the two forms of the rewritten loop is of historical interest: Early C compilers are said to have recognized the first pattern (without the
keyword) where the address used to access the register was a constant, and avoided the undesirable optimization for such accesses [GWYN]. However, they did not have the same ability when the access was through pointer variable in which the address had been stored, especially not when the use of such a variable was far removed from the last assignment to it. Thevolatile keyword was intended to allow both forms of the loop to work as expected.volatile The use case exemplified by the loop above has since become idiomatic and is being extensively relied on in today’s software even beyond reading I/O registers.
As a representative example, consider the Linux kernel which relies on
in its implementation of synchronization primitives such as spin locks, or for performance counters. The variables that are operated on by these primitives are typically declared to be of unqualified (i.e., nonvolatile ) scalar types and allocated in ordinary memory. In serial code, for maximum efficiency, each such variable is read and written just like any other variable, with its value cached in a CPU register as compiler optimizations permit. At well-defined points in the code where such a variable may be accessed by more than one CPU at a time, the caching must be prevented and the variable must be accessed using the specialvolatile semantics. To achieve that, the kernel defines two macros:volatile , andREAD_ONCE , in whose terms the primitives are implemented. Each of the macros prevents the compiler optimization by casting the address of its argument to aWRITE_ONCE and accessing the variable via an lvalue of thevolatile T * -qualified typevolatile (whereT is one of the standard scalar types). Other primitives gurantee memory synchronization and visibility but those are orthogonal to the subject of this paper. See [P0124R5].T Similar examples can be found in other system or embedded programs as well as in many other pre-C11 and pre-C++11 code bases that don’t rely on the Atomic types and operations newly introduced in those standards. . They are often cited in programming books [CBOOK] and in online articles [INTRO] [WHY] [WHYC].
3.4.2. C89 intent
[RATIONALE] lays out the intent for 
The C89 Committee concluded that about the only thing a strictly conforming program can do in a signal handler is to assign a value to a volatile static variable which can be written uninterruptedly and promptly return.
[…]
: No cacheing through this lvalue: each operation in the abstract semantics must be performed (that is, no cacheing assumptions may be made, since the location is not guaranteed to contain any previous value). In the absence of this qualifier, the contents of the designated location may be assumed to be unchanged except for possible aliasing.volatile […]
A
object is an appropriate model for a memory-mapped I/O register. Implementors of C translators should take into account relevant hardware details on the target systems when implementing accesses tostatic volatile objects. For instance, the hardware logic of a system may require that a two-byte memory-mapped register not be accessed with byte operations; and a compiler for such a system would have to assure that no such instructions were generated, even if the source code only accesses one byte of the register. Whether read-modify-write instructions can be used on such device registers must also be considered. Whatever decisions are adopted on such issues must be documented, asvolatile access is implementation-defined. Avolatile object is also an appropriate model for a variable shared among multiple processes.volatile A
object appropriately models a memory-mapped input port, such as a real-time clock. Similarly, astatic const volatile object models a variable which can be altered by another process but not by this one.const volatile […]
A cast of a value to a qualified type has no effect; the qualification (
, say) can have no effect on the access since it has occurred prior to the cast. If it is necessary to access a non-volatile object usingvolatile semantics, the technique is to cast the address of the object to the appropriate pointer-to-qualified type, then dereference that pointer.volatile […]
The C89 Committee also considered requiring that a call to
restore the calling environment fully, that is, that upon execution oflongjmp , all local variables in the environment oflongjmp have the values they did at the time of thesetjmp call. Register variables create problems with this idea. Unfortunately, the best that many implementations attempt with register variables is to save them inlongjmp at the time of the initialjmp_buf call, then restore them to that state on each return initiated by asetjmp call. Since compilers are certainly at liberty to change register variables to automatic, it is not obvious that a register declaration will indeed be rolled back. And since compilers are at liberty to change automatic variables to register if their addresses are never taken, it is not obvious that an automatic declaration will not be rolled back, hence the vague wording. In fact, the only reliable way to ensure that a local variable retain the value it had at the time of the call tolongjmp is to define it with thelongjmp attribute.volatile 
3.4.3. Intent in C++
To match ANSI C, the
modifier was introduced to help optimizer implementers. I am not at all sure that the syntactic parallel withvolatile is warranted by semantic similarities. However, I never had strong feelings aboutconst and see no reason to try to improve on the ANSI C committee’s decisions in this area.volatile 
As threads and a formal model were added to C++ it was unclear what role 
3.5. Current Wording
C++ has flaws, but what does that matter when it comes to matters of the heart? We love what we love. Reason does not enter into it. In many ways, unwise love is the truest love. Anyone can love a thing because. That’s as easy as putting a penny in your pocket. But to love something despite. To know the flaws and love them too. That is rare and pure and perfect.
― The Wise Programmer’s Fear in the style of [WMF]
The above description doesn’t tell us how 
Program execution [intro.execution]
Accesses through
glvalues are evaluated strictly according to the rules of the abstract machine.volatile Reading an object designated by a
glvalue, modifying an object, calling a library I/O function, or calling a function that does any of those operations are all side effects, which are changes in the state of the execution environment. Evaluation of an expression (or a subexpression) in general includes both value computations (including determining the identity of an object for glvalue evaluation and fetching a value previously assigned to an object for prvalue evaluation) and initiation of side effects. When a call to a library I/O function returns or an access through avolatile glvalue is evaluated the side effect is considered complete, even though some external actions implied by the call (such as the I/O itself) or by thevolatile access may not have completed yet.volatile 
Data races [intro.races]
Two accesses to the same object of type
volatile do not result in a data race if both occur in the same thread, even if one or more occurs in a signal handler. For each signal handler invocation, evaluations performed by the thread invoking a signal handler can be divided into two groups A and B, such that no evaluations in B happen before evaluations in A, and the evaluations of suchstd :: sig_atomic_t volatile objects take values as though all evaluations in A happened before the execution of the signal handler and the execution of the signal handler happened before all evaluations in B.std :: sig_atomic_t 
Forward progress [intro.progress]
The implementation may assume that any thread will eventually do one of the following:
terminate,
make a call to a library I/O function,
perform an access through a
glvalue, orvolatile 
perform a synchronization operation or an atomic operation
During the execution of a thread of execution, each of the following is termed an execution step:
termination of the thread of execution,
performing an access through a
glvalue, orvolatile 
completion of a call to a library I/O function, a synchronization operation, or an atomic operation.
Class member access [expr.ref]
Abbreviating postfix-expression.id-expression as
,E1 . E2 is called the object expression. IfE1 is a bit-field,E2 is a bit-field. The type and value category ofE1 . E2 are determined as follows. In the remainder of [expr.ref], cq represents eitherE1 . E2 or the absence ofconst and vq represents eitherconst or the absence ofvolatile . cv represents an arbitrary set of cv-qualifiers.volatile 
If
is a non-static data member and the type ofE2 is “cq1 vq1 X”, and the type ofE1 is “cq2 vq2 T”, the expression designates the named member of the object designated by the first expression. IfE2 is an lvalue, thenE1 is an lvalue; otherwiseE1 . E2 is an xvalue. Let the notation vq12 stand for the “union” of vq1 and vq2; that is, if vq1 or vq2 isE1 . E2 , then vq12 isvolatile . Similarly, let the notation cq12 stand for the “union” of cq1 and cq2; that is, if cq1 or cq2 isvolatile , then cq12 isconst . Ifconst is declared to be aE2 member, then the type ofmutable is “vq12 T”. IfE1 . E2 is not declared to be aE2 member, then the type ofmutable is “cq12 vq12 T”.E1 . E2 
The cv-qualifiers [dcl.type.cv]
The semantics of an access through a
glvalue are implementation-defined. If an attempt is made to access an object defined with avolatile -qualified type through the use of a non-volatile glvalue, the behavior is undefined.volatile [ Note:
is a hint to the implementation to avoid aggressive optimization involving the object because the value of the object might be changed by means undetectable by an implementation. Furthermore, for some implementations,volatile might indicate that special hardware instructions are required to access the object. See [intro.execution] for detailed semantics. In general, the semantics ofvolatile are intended to be the same in C++ as they are in C. —end note]volatile 
Non-static member functions [class.mfct.non-static]
A non-static member function may be declared
,const , orvolatile . These cv-qualifiers affect the type of theconst volatile pointer. They also affect the function type of the member function; a member function declaredthis is aconst member function, a member function declaredconst is avolatile member function and a member function declaredvolatile is aconst volatile member function.const volatile 
The this pointer [class.this]
In the body of a non-static member function, the keyword
is a prvalue expression whose value is the address of the object for which the function is called. The type ofthis in a member function of a classthis isX . If the member function is declaredX * , the type ofconst isthis , if the member function is declaredconst X * , the type ofvolatile isthis , and if the member function is declaredvolatile X * , the type ofconst volatile isthis .const volatile X * 
semantics apply involatile member functions when accessing the object and its non-static data members.volatile 
Constructors [class.ctor]
A constructor can be invoked for a
,const orvolatile object.const volatile andconst semantics are not applied on an object under construction. They come into effect when the constructor for the most derived object ends.volatile 
Destructors [class.dtor]
A destructor is used to destroy objects of its class type. The address of a destructor shall not be taken. A destructor can be invoked for a
,const orvolatile object.const volatile andconst semantics are not applied on an object under destruction. They stop being in effect when the destructor for the most derived object starts.volatile 
Overloadable declarations [over.load]
Parameter declarations that differ only in the presence or absence of
and/orconst are equivalent. That is, thevolatile andconst type-specifiers for each parameter type are ignored when determining which function is being declared, defined, or called.volatile 
Built-in operators [over.built]
In the remainder of this section, vq represents either
or no cv-qualifier.volatile For every pair (T, vq), where T is an arithmetic type other than
, there exist candidate operator functions of the formbool vq T & operator ++ ( vq T & ); T operator ++ ( vq T & , int ); For every pair (T, vq), where T is an arithmetic type other than
, there exist candidate operator functions of the formbool vq T & operator -- ( vq T & ); T operator -- ( vq T & , int ); For every pair (T, vq), where T is a cv-qualified or cv-unqualified object type, there exist candidate operator functions of the form
T * vq & operator ++ ( T * vq & ); T * vq & operator -- ( T * vq & ); T * operator ++ ( T * vq & , int ); T * operator -- ( T * vq & , int ); For every quintuple (C1, C2, T, cv1, cv2), where C2 is a class type, C1 is the same type as C2 or is a derived class of C2, and T is an object type or a function type, there exist candidate operator functions of the form
cv12 T & operator ->* ( cv1 C1 * , cv2 T C2 ::* ); For every triple (L, vq, R), where L is an arithmetic type, and R is a promoted arithmetic type, there exist candidate operator functions of the form
vq L & operator = ( vq L & , R ); vq L & operator *= ( vq L & , R ); vq L & operator /= ( vq L & , R ); vq L & operator += ( vq L & , R ); vq L & operator -= ( vq L & , R ); For every pair (T, vq), where T is any type, there exist candidate operator functions of the form
T * vq & operator = ( T * vq & , T * ); For every pair (T, vq), where T is an enumeration or pointer to member type, there exist candidate operator functions of the form
vq T & operator = ( vq T & , T ); For every pair (T, vq), where T is a cv-qualified or cv-unqualified object type, there exist candidate operator functions of the form
T * vq & operator += ( T * vq & , std :: ptrdiff_t ); T * vq & operator -= ( T * vq & , std :: ptrdiff_t ); For every triple (L, vq, R), where L is an integral type, and R is a promoted integral type, there exist candidate operator functions of the form
vq L & operator %= ( vq , L & , R ); vq L & operator <<= ( vq , L & , R ); vq L & operator >>= ( vq , L & , R ); vq L & operator &= ( vq , L & , R ); vq L & operator ^= ( vq , L & , R ); vq L & operator |= ( vq , L & , R ); 
Here are salient appearances of 
Type qualifiers
An object that has
-qualified type may be modified in ways unknown to the implementation or have other unknown side effects. Therefore any expression referring to such an object shall be evaluated strictly according to the rules of the abstract machine. Furthermore, at every sequence point the value last stored in the object shall agree with that prescribed by the abstract machine, except as modified by the unknown factors mentioned previously.† What constitutes an access to an object that hasvolatile -qualified type is implementation-defined.volatile † A
declaration may be used to describe an object corresponding to a memory-mapped input/output port or an object accessed by an asynchronously interrupting function. Actions on objects so declared shall not be "optimized out" by an implementation or reordered except as permitted by the rules for evaluating expressions.volatile 
3.6. Why the proposed changes?
Only priests and fools are fearless and I’ve never been on the best of terms with God.
— The Name of The Wind [NotW]
3.6.1. External modification
We’ve shown that 
- 
     Shared memory with untrusted code, where volatile 
- 
     Signal handling, where at any time in a program’s execution a signal can occur, and the optimizer must therefore make sure that volatile 
- 
     setjmp longjmp setjmp volatile atomic_signal_fence 
- 
     Various other external modifications such as special hardware support—e.g. memory-mapped registers—where the compiler cannot assume that memory doesn’t change or that writes aren’t synchronizing externally. 
- 
     Marking that an infinite loop has side-effects and is therefore not undefined behavior (this can also be done with atomic or I/O operations). 
- 
     Casting pointers to volatile volatile 
- 
     Enforcing control dependencies and preventing compiler value speculation (such as through feedback-directed optimization) as discussed in [CONTROL]. 
- 
     Avoiding value speculation around some hand-rolled implementations of memory_order_consume 
As [SEBOR] lays out there have been wording issues around this usage. [TROUBLE] and [ACCESS_ONCE] make a similar case. This paper doesn’t try to
address those issues. We don’t see a reason to change existing syntax denoting
external modification in this paper: this paper rather focuses on deprecation of invalid or misleading uses of 
A new language would likely do things differently, but this paper isn’t about
creating a new language. Notably, [D] and [Rust] took different approaches
(
Note that an important aspect of external modification for 
3.6.2. Compound assignment
We propose to deprecate, and eventually remove, 
 We would like guidance on 
 There’s a related problem in [TROUBLE] with chained assignments of 
3.6.3. volatile 
   
Let’s consider what 
It’s worth noting that 
We propose to deprecate, and eventually remove, 
Our goal is to avoid the ambiguity where an aggregate is sometimes 
- 
     Mandate that member functions volatile 
- 
     Allow the aggregate declaration itself to be volatile struct volatile my_hardware { /* ... */ }; 
- 
     Disallow volatile 
Either of the first two approaches approaches clearly tell the compiler that
every data member access should be done in a 
 Which of the above approaches (deprecate 
 If we keep 
 If we keep 
 It is unclear how 
 We would like guidance on whether 
3.6.4. volatile 
   Partial template specializations involving 
- 
     numeric_limits 
- 
     tuple_size 
- 
     tuple_element 
- 
     variant_size 
- 
     variant_alternative 
- 
     atomic free function overloads 
- 
     atomic 
We propose to deprecate, and eventually remove, 
As of this writing, 
| Directory | count | 
|---|---|
|  | 12 | 
|  | 12 | 
|  | 175 | 
|  | 2 | 
|  | 48 | 
|  | 2 | 
|  | 1 | 
|  | 1 | 
|  | 71 | 
|  | 8 | 
|  | 1 | 
 Should we go further and forbid 
3.6.5. volatile 
   
- 
     A non-lock-free atomic can be volatile 
- 
     Read-modify-write operations are implemented as either loops which retry, locked instructions (which still perform a load and a store), as transactional memory operations, or as memory controller operations. Only the last of these can truly be said to touch each byte exactly once, and these hardware implementations are far from the norm. 
We propose to deprecate, and eventually remove, true.
 We would like guidance on whether other read-modify-write operations
should be maintained with implementation-defined semantics. Specifically, 
The same guidance would apply to atomic free function overloads.
3.6.6. volatile 
   Marking parameters as 
Similarly, 
We propose to deprecate, and eventually remove, non-reference and non-pointer 
4. The Slow Regard of Syntactic Things
This paper is for all the slightly broken features out there.
is one of you. You are not alone. You are all beautiful to me.volatile — The Slow Regard of Syntactic Things in the style of [SRST]
This proposal tries to balance real-world usage, real-world breakage, frequent
gotchas, and overly chatty features which aren’t actually used. The author
thinks it strikes the right balance, but may be wrong. 
It is important that 
5. The Doors of Stone
This section will hold future work, wording, etc. It will be published based on Committee feedback, when it is ready. The author wants to give the Committee a perfectly worded paper. They deserve it.
Here are items which could be discussed in the future:
- 
     asm asm volatile 
- 
     Standardize a library-like replacement for volatile peek poke 
6. Acknowledgements
Early drafts were reviewed by the C++ Committee’s Direction Group, Thomas Rodgers, Arthur O’Dwyer, John McCall, Mike Smith, John Regehr, Herb Sutter, Shafik Yaghmour, Hans Boehm, Richard Smith, Will Deacon, Paul McKenney. Thank you for in-depth feedback, and apologies if I mistakenly transcribed your feedback.
Patrick Rothfuss, for writing amazing books. May he take as much time as needed to ensure forward progress of book 3.
7. Examples
Here are dubious uses of 
struct foo { int a : 4 ; int b : 2 ; }; volatile foo f ; // Which instructions get generated? Does this touch the bytes more than once? f . a = 3 ; 
struct foo { volatile int a : 4 ; int b : 2 ; }; foo f ; f . b = 1 ; // Can this touch a? 
union foo { char c ; int i ; }; volatile foo f ; // Must this touch sizeof(int) bytes? Or just sizeof(char) bytes? f . c = 42 ; 
volatile int i ; // Can each of these touch the bytes only once? i += 42 ; ++ i ; 
volatile int i , j , k ; // Does this reload j before storing to i? i = j = k ; 
struct big { int arr [ 32 ]; }; volatile _Atomic struct big ba ; struct big b2 ; // Can this tear? ba = b2 ; 
int what ( volatile std :: atomic < int > * atom ) { int expected = 42 ; // Can this touch the bytes more than once? atom -> compare_exchange_strong ( expected , 0xdead ); return expected ; } 
void what_does_the_caller_care ( volatile int ); 
volatile int nonsense ( void ); 
struct retme { int i , j ; }; volatile struct retme silly ( void ); 
struct device { unsigned reg ; device () : reg ( 0xc0ffee ) {} ~ device () { reg = 0xdeadbeef ; } }; volatile device dev ; // Initialization and destruction aren’t volatile.