There is a qualifier called volatile
very popular in embedded code bases, but sometimes it is used improperly since some misconceptions about it. Let's see how it really works.
Definition
Qualifying a variable as volatile
tells something very straightforward to the compiler, it must not apply any kind of optimization on the variable.
In this sentence a key role is played by the compiler, so, how can we see this working? I made an example that show this behaviour.
#include <iostream>
int main(){
int v{42};
int w{21};
int * p{&v}; // p points to v
p--; // now p points to w
*p = 77; // changing the value pointed by p, now w!
std::cout << v << std::endl; // print 42
std::cout << w << std::endl; // print 21 !!!
return 0;
}
As shown in the previous example, that is available here, using gcc 14.2 on a x86-64 architecture without any compiler options. The compiler optimize stuffs and decide to did not apply the change of the value pointed by p
after its decrementation. It catches a probably Undefined Behavior and avoid it. It is a good thing uh? Absolutely yes, but, for teaching purpose I want it! So, how can I fix this compiler optimization?
#include <iostream>
int main(){
int v{42};
int w{21};
int * p{&v}; // p points to v
p--; // now p points to w
*p = 77; // changing the value pointed by p, now w!
std::cout << &v << std::endl; // I'm using the v-address
std::cout << &w << std::endl; // I'm using the w-address
std::cout << v << std::endl; // print 42
std::cout << w << std::endl; // print 77
return 0;
}
Now that I'm using the address of the variables v
and w
the compiler is forced to use them and it can't avoid to change the value pointed by p
. Example available here.
Use of volatile
But it is annoying change the program to obtain what I wanted from the beginning because of decisions made by the compiler, even if are good ones. I'm the programmer and I want the full control of what it is generated by my code. Therefore, we can use volatile
for our purpose:
#include <iostream>
int main(){
int v{42};
volatile int w{21};
int * p{&v}; // p points to v
p--; // now p points to w
*p = 77; // changing the value pointed by p, now w!
std::cout << v << std::endl; // print 42
std::cout << w << std::endl; // now, finally, prints 77!!!
return 0;
}
Declaring the variable w
as voltile we forced the compiler to not apply any kind of optimization exactly what we want (also if we want a bad thing). Why apply that qualifier only on w
variable? Because it is its address the subject of the optimization. Example available here
Volatile and concurrency
Historically volatile word is believed a synchronization mechanism in concurrency programming. This is wrong, volatile
doesn't guarantee the atomicity of the reading / writing operations over the variable where it is applied, use instead std::atomic
, mutexes or condition_variables
to do so.
Volatile and hardware
Another way to think about volatile is that it tell to compiler that the variable can be changed by something external from the code. Is this the case of low level programming as, for example, embedded environment. A variable can be used by a peripheral and it couldn't be evident from the code. In this cases volatile
word avoid the compiler made decision distruptive for our program.
Key takeawayas
After seen volatile
in action we can summarize some takeaway points:
- Use
volatile
to deactivate possible compiler optimizations. - Use volatile in hardware related code.
- Don't use it as a synchronization method.
Use volatile
with awareness and see you on next episode!
Top comments (0)