Not understanding what the volatile keyword does and how it should be used is specially problematic because you get any errors or warnings. Your code will silently fail or present you with wrong results which you may consider valid if don’t know what’s happening.
You probably don’t need to use it often and that’s why this is such an underrated keyword. But it exists for a purpose. It allows you to say this to the compiler:
Please Mr Compiler, do not optimize code that use this volatile variable, because it is, well, volatile. I mean, it can really change its value at any time and you are not capable of noticing it! Or I’m just doing stuff you don’t understand, so please don’t touch it. You have been advised.
In which circumstances can it happen? Let the examples begin. But before, a brief note: results will vary depending on the compiler you use. The compiler itself may decide to accept your “hints” or not. However, I think it’s safe to say that nowadays you may consider any compiler clever enough to consider these kind of “hints”. The following examples were tested with MinGW (the GCC for Windows). Also, note that any optimizations (the -O argument of GCC compiler) were used.
How Fast Can You Decrement a Huge Value?
Suppose you want to measure the time a program takes to decrement a huge integer value until it reaches zero. You would write something like this:
The lines that matter are 12 and 13. The other ones are there essentially for printing the elapsed time in seconds. Can you guess how many time it takes? Zero seconds (well, almost). Does it means you have a super fast computer? No. You can try to increase the initial value of the variable and the result will be the same. But why? Because the compiler is not stupid: it notices that you do nothing in the for loop so it decrements all the values at once and proceed with the remaining code. Logically, this is absolutely right but it doesn’t allow you to measure the actual time that it would take if any optimizations were implicitly applied by the compiler. Let’s bring the volatile keyword to the rescue then. If you prepend the volatile keyword to the variable declaration, as in:
volatile uint64_t i = 999999999;
Now the program runs as expected and it takes about 8 seconds to finish (on my computer; it depends on the CPU speed and how busy it is).
Adding Some Complexity
Instead of doing nothing inside the for loop, what happens if we don’t use volatile but we use the value of i to compute the value of j?
Well, this means the final value of j will be computed with the last value of i. This is also easy for the compiler to optimize so it will take zero seconds do finish unless you use the volatile keyword.
However, if the computation of j depends on the result of the previous iteration, i.e.:
Then, the compiler will actually have to compute all the iterations until we get the final result. Still, you may be assigning a value to j but if you don’t use it on the remaining code it’s actually the same thing of doing nothing (if it’s not used, don’t compute it). That’s why we need to print it (this is our “do something”). In this case, the program takes about 2 seconds to finish. It’s still less than the previous example. Why? Well, that’s because the compiler is still doing some optimizations we are not aware of in order to execute the code faster.
Finally, if both variables are declared as volatile, then it would take about 9 seconds, just a bit more than the first example.
So if you need ABSOLUTELY no optimizations on a given variable (“Mr Compiler, please, don’t touch it!”), use the volatile keyword.