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.

Categorized in:

Tagged in: