Volatile and Atomic variables in C++

By , last updated August 31, 2019

There are several types of variables in C++ with different variable scopes. In this article we will talk about volatile vs atomic variables in C++ and how they behave differently in different scenarios.

C++ Volatile Variables

In C++, the volatile keyword placed before the variable indicates that the value of a variable may change between different accesses, even if it does not appear to be modified. Possible values changes include operating system, the hardware, or a concurrently executing thread.

The following example will declare an integer as volatile:

volatile int myVariable;

Volatile is a member modifier and it must precede the member type int and the name of the variable.

Consider the following code example:

int cond = 20;
while( cond == 20)
{
	//condition is true
}

What happens when this code gets compiled is that the compiler will optimize the while loop condition, as it sees that value of ‘cond’ is not changing. So rather than fetching value of ‘cond’ again and again from memory and comparing it with 20, it simply makes ‘while (true)‘ instead of ‘while (cond == 20)‘. The compiler does so in order to run the code faster, obviously fetching value of ‘cond’ again and again from memory will make the program run slower.

Here’s how the compiled version of the code will look like:

// int cond = 20; // Unless used, this will be removed too.
while(true)
{
	//condition is true
}

This explains what code optimization is and what its pros are. However, there are situations when this optimization is not desired and it happens when some external agency out of the program is changing the value of a variable of which compiler is not aware of and optimization, in this case won’t produce desired results rather will have a negative impact on the output.

The keyword ‘volatile’ is created for such situations and it simply stops the compiler from making any assumption about the variable. If an object is declared as volatile, every time that variable is called its value is loaded from memory.

The modified example, with the volatile keyword will look like this:

volatile int cond = 20;
while(cond == 20)
{
	//condition is true
}

In this modified piece of code the compiler will not optimize the variable ‘cond’, rather whenever while loop is executed it fetches the value of ‘cond’ from memory.

Volatile use cases

Volatile variables are mostly useless on modern machines with latest compilers. A volatile keyword was introduced to prevent compiler optimizations of some conditions and ensure access of all interested threads to the variable. Modern hardware may suppress and reorder certain accesses thus making use of the volatile keyword alone not safe.

Given the situation, modern standard C++ has added atomic access variables that guarantees certain behavior across all threads.

C++ Atomic Variables

There are conditions when an object is at the same time accessed by different threads, resulting in an undefined behavior. This situation is called Data race.

C++ has an Atomic Type to solve this problem of Data Race. Objects of type Atomic are protected from data races, and if one thread tries to write to atomic objects while another thread is extracting values from it, the result is well defined. This comes from the fact that atomic operations modify data on a single clock tick. This makes it impossible for other threads to access the variable at the same time. If the variable cannot be modified during a single tick, the compiler will insert thread guards before and after the variable.

C++ also has ‘mutex’ locks, that locks the value of objects when its manipulated by one thread and other threads are only allowed to change the value when mutex unlocks the object.

Since atomic operation is indivisible, a second atomic operation on the same object from a different thread can obtain the state of the object only before or after the first atomic operation.

Atomic operation establishes ordering requirements based on memory_order argument. This helps in inhibiting compiler optimizations that violate the ordering requirements.

Atomic variable examples

Fundamental data types in C++ are not atomic. In order to ensure the atomicity of the variable C++ introduced std::atomic class template that is to be used with types like int, double and so on.

For a boolean type there is an atomic_flag that is guaranteed to be lock-free.

In order to use atomicity in your program, use the template argument std::atomic on the attributes. Note, that you can’t make your whole class atomic, just it’s attributes.

#include <atomic>  
class Foo
{
    std::atomic<int> bar;

    public:
      Foo() {
        bar=0;
      }
      void doSmth() {
        bar++;
      } 
};

You don’t need to use volatile along with std::atomic. The keyword volatile does not imply atomic access and has no bearing on atomic access whatsoever.

Senior Software Engineer developing all kinds of stuff.