C++ Series Summary - volatile Keyword

Posted by sanchez77 on Wed, 15 May 2019 07:04:01 +0200

Preface

volatile means changeable in Chinese, but this changeable means different from mutable. Mutable refers to compile-time variability. By default, the grammar compiler will not let us modify some variables, but adding mutable lets the compiler know that we have a tough attitude to modify. volatile variability refers to runtime variability, which is a change that compilers cannot perceive, so that compilers do not blindly optimize.

How volatile affects compilation results

Example 1

The compiler optimizes dead code that it deems useless.

int main()
{
    int* reg = 0x123456; // Suppose 0x123456 is the address of a special register
    *reg = 0x1;
    *reg = 0x2;
    *reg = 0x3;
    *reg = 0x4;
    return 0;
}

After compiling the above code through g++ a.cpp-O2, you will find that only * reg = 0x4; it works, and other statements are optimized.

   0x0000000000400540 <+0>: movl   $0x4,0x123456
   0x000000000040054b <+11>:    xor    %eax,%eax
   0x000000000040054d <+13>:    retq 

Normally, this doesn't matter, but the code in the example above actually initializes the state of a device, and assignment of any state can't be omitted. Otherwise, it may cause the device to work abnormally, so volatile is needed. When volatile is added, no statement is optimized after compiling through g++ a.cpp-O2 as well.

   0x0000000000400540 <+0>: movl   $0x1,0x123456
   0x000000000040054b <+11>:    xor    %eax,%eax
   0x000000000040054d <+13>:    movl   $0x2,0x123456
   0x0000000000400558 <+24>:    movl   $0x3,0x123456
   0x0000000000400563 <+35>:    movl   $0x4,0x123456
   0x000000000040056e <+46>:    retq 

Example two

Because registers are fast, compilers use registers to optimize and avoid frequent memory manipulation.
Students who have played MCU or ARM board must have written the horse-running lamp program. The following program can simply simulate the flashing of LED lights.

int main()
{
    int* a = (int*)0x123456; 
    for( int i = 0; i < 1000; ++i ){
        *a = ~(*a); // Recurrent reversal, switching on and off LED lights
        sleep( 1 );
    }
    return 0;
}

Compiled by g++ a.cpp-O 2, you will find that the value after each inversion is stored in the register edx, and only the value in the EDX is saved to 0x123456 at the end, which is equivalent to only switching on the LED lamp once, which is not in line with expectations.

   0x0000000000400540 <+0>: mov    0x123456,%edx
   0x0000000000400547 <+7>: mov    $0x3e8,%eax
   0x000000000040054c <+12>:    nopl   0x0(%rax)
   0x0000000000400550 <+16>:    sub    $0x1,%eax
   0x0000000000400553 <+19>:    not    %edx
   0x0000000000400555 <+21>:    jne    0x400550 <main+16>
   0x0000000000400557 <+23>:    mov    %edx,0x123456
   0x000000000040055e <+30>:    xor    %eax,%eax
   0x0000000000400560 <+32>:    retq 

When volatile is added, every negative result will be stored in 0x123456, and 1000 times of switching on and off the LED lamp meets the expectation.

=> 0x0000000000400540 <+0>: mov    $0x3e8,%edx
   0x0000000000400545 <+5>: nopl   (%rax)
   0x0000000000400548 <+8>: mov    0x123456,%eax
   0x000000000040054f <+15>:    sub    $0x1,%edx
   0x0000000000400552 <+18>:    not    %eax
   0x0000000000400554 <+20>:    mov    %eax,0x123456      # Each negative result is stored in 0x123456
   0x000000000040055b <+27>:    jne    0x400548 <main+8>
   0x000000000040055d <+29>:    xor    %eax,%eax
   0x000000000040055f <+31>:    retq

The above example can also be reversed, that is, the program counts the number of times the external switch LED lights are switched (by interrupt sensing), if optimized using registers, the CPU may perceive only one change.

Example three

The compiler can rearrange instructions to facilitate instruction streaming and achieve optimization purposes.

int main()
{
    int* a = (int*)0x123456;  // 0x123456 is the address of a device
    *a = 0x1; // Turn on the device
    int* b = (int*)0x654321; // 0x654321 is the data reading address of the device
    printf( "%d\n", *b ); // Read the address's data
    return 0;;
}

The logic of the above code is to read data from the specified address of a device after it is turned on. There is a causal relationship, but the compiler cannot recognize it. So after compiling with g++ a.cpp-O2, you will find that int* b = (int*)0x654321 is ahead of schedule and the program does not meet expectations.

=> 0x0000000000400590 <+0>: sub    $0x8,%rsp
   0x0000000000400594 <+4>: mov    0x654321,%esi # Read data from 0x654321 first
   0x000000000040059b <+11>:    movl   $0x1,0x123456 # Then set 0x1 to 0x123456
   0x00000000004005a6 <+22>:    mov    $0x400760,%edi
   0x00000000004005ab <+27>:    xor    %eax,%eax
   0x00000000004005ad <+29>:    callq  0x400530 <printf@plt>
   0x00000000004005b2 <+34>:    xor    %eax,%eax
   0x00000000004005b4 <+36>:    add    $0x8,%rsp
   0x00000000004005b8 <+40>:    retq

When volatile is used to modify a and b, the order of instruction execution is in line with expectations.

=> 0x0000000000400590 <+0>: sub    $0x8,%rsp
   0x0000000000400594 <+4>: movl   $0x1,0x123456 # First set up
   0x000000000040059f <+15>:    mov    0x654321,%esi # After read
   0x00000000004005a6 <+22>:    mov    $0x400760,%edi
   0x00000000004005ab <+27>:    xor    %eax,%eax
   0x00000000004005ad <+29>:    callq  0x400530 <printf@plt>
   0x00000000004005b2 <+34>:    xor    %eax,%eax
   0x00000000004005b4 <+36>:    add    $0x8,%rsp
   0x00000000004005b8 <+40>:    retq

epilogue

Volatile guarantees that access to the modified variable cannot be optimized within a single thread of execution, and access to the volatile variable of another volatile variable in order or after will not be reordered.
From the above examples, we can see that volatile is commonly used in embedded development, but it is seldom used in general application development. It is generally used with signal processing functions.

volatile does not guarantee multithreading security.

The above code will crash, just look at the meaning.
gcc version 4.8.5 20150623

Topics: C++