Volatile Keyword : What Happens Under The Hood
How does the volatile variable work behind the scenes?
Google volatile variable and you know that without the volatile keyword the compiler will cache results and cause programs incorrectness. But, why does it get wrong? Ever checked the assembly code?
First, the definition of volatile according to a great article on Dr.Dobbs by Andrei Alexandrescu has written about, very well:
The
volatile
keyword was devised to prevent compiler optimizations that might render code incorrect in the presence of certain asynchronous events.
So what effect does volatile have on a compiler?
Concretely, let’s check out two examples with the help of constants (const). We know that constants are, well, constants. So it is impossible to modify the value, except in C++. So take these programs A and B which tries to modify a constant but one with a volatile keyword.
// Program A #include <cstdio>
int main(){
const volatile int i = 50;
int * pi = const_cast<int *>(&i);
printf("%d\n", i);
*pi = 20;
printf("%d\n", i);
}
And this program B:
// Program B #include <cstdio>
int main(){ const int i = 50; int * pi = const_cast<int *>(&i); printf("%d\n", i); *pi = 20; printf("%d\n", i); }
Output of Program A:
50 20
Output of Program B:
50 50
OK. These are facts, why? What happened? Let’s do some debugging via gdb. Compile the programs with symbols ( g++ -o
Program A gdb output:
main () at const_cast.cpp:4 4 const volatile int i = 50; (gdb) s 5 int * pi = const_cast<int *>(&i); (gdb) p i $1 = 50 (gdb) s 6 printf("%d\n", i); (gdb) s 50 7 *pi = 20; (gdb) p *pi $2 = 50 (gdb) s 8 printf("%d\n", i); (gdb) p *pi $3 = 20 (gdb) s 20 9 }
Program B gdb output:
main () at const_cast2.cpp:44 const int i = 50; (gdb) s 5 int * pi = const_cast<int *>(&i); (gdb) s 6 printf("%d\n", i); (gdb) p i $1 = 50 (gdb) p *pi $2 = 50 (gdb) s 50 7 *pi = 20; (gdb) s 8 printf("%d\n", i); (gdb) p i $3 = 20 (gdb) p * pi $4 = 20 (gdb) p $5 = 20 (gdb) s 50 9 }
The gdb output of program B mysteriously prints the *pi as 50 and not 20, even though we have checked that *pi is really 20. Why? Now, we need some truth serums – assembly code.
Let’s get some assembly code ( g++ -g -c -Wa,-alh ):
Assembly of program A, take note at bolded parts.
6:const_cast.cpp **** *pi = 20; 42 .loc 1 7 0 43 002d 8B442418 movl 24(%esp), %eax 44 0031 C7001400 movl $20, (%eax) 44 0000 7:const_cast.cpp **** printf("%d\n", i); 45 .loc 1 8 0 46 0037 8B44241C movl 28(%esp), %eax 47 003b 89442404 movl %eax, 4(%esp) 48 003f C7042400 movl $.LC0, (%esp) 48 000000 49 0046 E8FCFFFF call printf
Assembly of program B, take note at bolded parts.
6:const_cast2.cpp **** *pi = 20; 41 .loc 1 7 0 42 002d 8B442418 movl 24(%esp), %eax 43 0031 C7001400 movl $20, (%eax) 43 0000 7:const_cast2.cpp **** printf("%d\n", i); 44 .loc 1 8 0 45 0037 C7442404 movl $50, 4(%esp) 45 32000000 46 003f C7042400 movl $.LC0, (%esp) 46 000000 47 0046 E8FCFFFF call printf
Note in Program B, line 45 indicates that the compiler just push the value “50” into the second argument of printf, rather than in Program Line 46 it pushes the value of i from the stack.
It should be obvious now : Program B, without the volatile keyword on , has led the compiler to believe the constant variable i does not change. As thus, the compiler optimizes and assumes value pointed by pi (*pi) is 50. However, the compiler is wrong. By adding the volatile keyword, we tell the compiler not to be too smart to get things wrong.