volatile is a pre-defined keyword that is used to specify that the value in the variable may change at any time during the program execution. It prevents the compiler from optimizing the instructions.
Syntax: <type-qualifier> <Datatype> <identifier>
Example: volatile int a;
To understand the concept of volatility in a precise manner, we need to know about the optimization levels in the GCC compilation. GCC compiler offers various optimization flags to optimize the program. They are O, O0, O1, O2, O3, -Ofast, -Og, -Os e.t.c.
#1 An example program using volatile type qualifier:
#include <stdio.h>
void main()
{
volatile int indicator = 0;
while(indicator == 0){
printf("Downloading Data .....");
}
}
Use the below command to apply level 1 – Optimization (O1) while compiling the program.
gcc -O1 program.c -o executable
Use the below command to decompile the assembly instructions using Objdump. Learn how to use Objdump by using the “man page” i.e. use the command “man objdump“.
objdump -d executable
Assembly instructions:
000000000000066a <main>:
66a: 53 push %rbx
66b: 48 83 ec 10 sub $0x10,%rsp
66f: c7 44 24 0c 00 00 00 movl $0x0,0xc(%rsp)
676: 00
677: 8b 44 24 0c mov 0xc(%rsp),%eax
67b: 85 c0 test %eax,%eax
67d: 75 21 jne 6a0 <main+0x36>
67f: 48 8d 1d ae 00 00 00 lea 0xae(%rip),%rbx # 734 <_IO_stdin_used+0x4>
686: 48 89 de mov %rbx,%rsi
689: bf 01 00 00 00 mov $0x1,%edi
68e: b8 00 00 00 00 mov $0x0,%eax
693: e8 a8 fe ff ff callq 540 <__printf_chk@plt>
698: 8b 44 24 0c mov 0xc(%rsp),%eax
69c: 85 c0 test %eax,%eax
69e: 74 e6 je 686 <main+0x1c>
6a0: 48 83 c4 10 add $0x10,%rsp
6a4: 5b pop %rbx
6a5: c3 retq
6a6: 66 2e 0f 1f 84 00 00 nopw %cs:0x0(%rax,%rax,1)
6ad: 00 00 00
Explanation:
At 66f, 0 is moving to 12 bytes above w.r.t to the stack pointer i.e. initializing the indicator variable with 0 (relative addressing mode). At 677 the value is again fetching back to register for the comparison at 67b i.e. in the while loop. If the result is false then 67d will be executed else 67f will be executed.
Now, If you closely observe the instructions 66f, 677, 67b, 698, and 69c always the value is directly fetching from the memory to register i.e from (0xc(%rsp)) to %eax for the comparison in the while loop. Due to this, we always have an updated value even if this value is changed by some other thread in the program execution.
#2 An example program without using volatile type qualifier:
#include <stdio.h>
void main()
{
int indicator = 0;
while(indicator == 0){
printf("Downloading Data .....");
}
}
Command to compile:
gcc -O1 program.c -o program
Decompile the assembly:
objdump -d program
Assembly instructions:
000000000000066a <main>:
66a: 53 push %rbx
66b: 48 8d 1d a2 00 00 00 lea 0xa2(%rip),%rbx # 714 <_IO_stdin_used+0x4>
672: 48 89 de mov %rbx,%rsi
675: bf 01 00 00 00 mov $0x1,%edi
67a: b8 00 00 00 00 mov $0x0,%eax
67f: e8 bc fe ff ff callq 540 <__printf_chk@plt>
684: eb ec jmp 672 <main+0x8>
686: 66 2e 0f 1f 84 00 00 nopw %cs:0x0(%rax,%rax,1)
68d: 00 00 00
Explanation:
If you observe, there is no comparison instruction in the above assembly program. If we think from the compiler’s perspective, the value of the ‘indicator’ will always be a ‘0’ because it is not being changed in the while loop & there is no point in comparing it again and again with ‘0’. Due to this, the compiler optimizes this instruction and prints the message continuously onto the screen without comparing.
But this creates a hell-like situation when an object is shared between multiple concurrent processes. Because any process can change the value at any time.