What is the use of volatile keyword in C/C++? What is the difference between declaring a variable volatile
and not declaring it as volatile
?
For most C programs, the purpose of an object is to hold the last thing that was written by code in the current execution context ("thread", CPU core, etc.). The easiest way for a compiler to keep track of an object's contents is to allocate space in storage, and treat reads and writes of that variable as reads and writes of that storage, but the storage itself does not represent the purpose of the code--it merely represents a means to an end. Given unsigned x;
, if a compiler sees x+=3; x+6;
, the easiest way to generate code would be to fetch x, add 3, store the result to x, then fetch x, add 6, and store that result. The intermediate load and store, however, are only needed when the compiler doesn't know how to achieve the same effect some other way. A better compiler given such code would often be able to simplify it to simply add 9.
Especially in embedded or systems code, however, the purpose of a program may include loading and storing certain values from certain storage locations in a certain sequence. Many real-world machines perform I/O by having hardware which is triggered by loads and stores of certain objects, and performs various actions in response. For example, it would not be uncommon to have an object which would under the right conditions cause any character sent to it to be passed along to a terminal. Storing 'H' and then 'i' to SERTX, for example, might send Hi
. Having a compiler try to simplify or consolidate such sequences (e.g. deciding to omit the store of 'H') would render the programs useless. A volatile
qualifier indicates to the compiler that while it would be free to consolidate most accesses to most objects, there are a few for which such accesses need to be performed precisely as written. Effectively, one could imagine that for each scalar type (e.g. "unsigned") there are functions
int volatile_load_unsigned(unsigned volatile *p);
void volatile_store_unsigned(unsigned volatile *p, usigned value);
whose behavior the compiler knows nothing about, and thus code like:
extern volatile unsigned x;
x+=3;
x+=6;
would be interpreted by the compiler as equivalent to:
extern volatile int x;
volatile_store_unsigned(&x, volatile_load_unsigned(&x)+3);
volatile_store_unsigned(&x, volatile_load_unsigned(&x)+6);
In fact, the machine code to perform a volatile store is on most systems the same as the code for an ordinary store (and does not generate any kind of function call) but only the part of the compiler that generates the final machine code would "know" that--everything else should treat the code as though it was a function whose effects the compiler knew nothing about.
Unfortunately, in the name of "optimization", some compilers have ceased
treating volatile accesses as calls to opaque functions. If code calls
a function whose inner workings the compiler knows nothing about, it
must assume that function may access any and all objects whose address
has ever been exposed to the outside world. Applying such treatment to
volatile
-qualified variables will allow code to construct an object in
ordinary storage, taking advantage of the ability to consolidate loads
and stores, store the address of that object to a volatile-qualified
pointer, triggering some other process which outputs the buffer. Code
would need to test volatile-qualified object to ensure that the contents
of the buffer had been output completely before doing anything else
with that storage, but on implementations that follow the "opaque function" model the compiler would ensure that all writes to the storage which occurred in code before the volatile store that triggers the output operation would in fact generate store instructions which precede the instruction for the volatile store.
Some compiler writers think that it is more useful to have compilers assume they don't need to generate stores to non-qualified variables before performing accesses to volatile-qualified ones. Certainly the Standard does not require that implementations acknowledge the possibility that writing to a volatile variable might trigger actions that could affect other things, but that doesn't mean that a quality compiler for systems where such constructs can be useful shouldn't support them. Unfortunately, the fact that the authors of the Standard didn't want to mandate behaviors which would be useful on some systems but not others, has been interpreted as suggesting that compiler writers shouldn't support such behaviors even on systems where they are useful.
Note that on some processors, ensuring that one CPU executes instructions in a certain sequence may not be sufficient to ensure that the effects of that instruction occur in sequence. Some processors include configuration options so that operations on some parts of memory will be guaranteed to occur in order even when that would reduce execution speed, and some processors include other means of controlling execution order, but such issues are separate from volatile
. On a quality implementation, volatile
will ensure that the processor is at least made aware of all reads and writes that need to occur; a programmer may need to perform additional steps to ensure that the processor actually perform them as indicated, but telling the processor to complete all pending operations before it does anything else won't do any good if the compiler hasn't told the processor that something needs to get written.