Open In App

volatile Qualifier in C++

Last Updated : 05 Jul, 2024
Comments
Improve
Suggest changes
Like Article
Like
Report

volatile keyword in C++ is used to declare variables that can be modified by external factors outside the program's control. This qualifier informs the compiler that the variable's value may change anytime, preventing certain optimizations that could lead to unexpected behavior. In this article, we will learn about the volatile qualifier and its usage in various scenarios.

Volatile Variables

In C++ the volatile keyword is used to declare a variable whose value can be changed by various external factors that are outside the program's control, such as hardware or a concurrently executing thread. This prevents the compiler from doing some optimizations assuming the variable value remains unchanged unless explicitly changed within the program. Following is the syntax to use the volatile keyword in C++.

Syntax to Define Volatile Variable

volatile type variable_Name;

Here,

  • type: denotes the data type of the volatile variable
  • variable_Name: denotes the name of the volatile variable.

Volatile Variables in Functions

Volatile variables within a function is very useful when we deal with hardware registers or shared memory in a multithreaded environment and handle concurrent operations. Volatile variables helps to maintain data consistency and also ensures that the compiler doesn't waste the computer power in optimizations for the variables as the value of the variable can change any time.

Example

The following program demonstrates the implementation of the above concept in C++.

C++
// C++ Program for using volatile variables in functions

#include <chrono>
#include <iostream>
#include <thread>
using namespace std;

// declare the volatile variable
volatile bool interruptFlag = false;

void interruptHandler() { interruptFlag = true; }

void mainLoop()
{
    while (!interruptFlag) {
        cout << "Working..." << endl;
        this_thread::sleep_for(chrono::milliseconds(500));
    }
    cout << "Interrupt received. Exiting." << endl;
}

int main()
{
    thread worker(mainLoop);
    this_thread::sleep_for(chrono::seconds(2));
    interruptHandler();
    worker.join();
    return 0;
}


Output

Working...
Working...
Working...
Interrupt received. Exiting.

Explanation: In the above program, the interruptFlag is declared volatile because it is shared between the main thread and the worker thread and it's value can change anytime. The volatile qualifier ensures that the worker thread always reads the most up to date value of the interruptFlag even if the compiler optimizes the read operation.

Volatile Member Variables in a Class

Volatile variables are used within a class when a class represents an hardware interface or when we deal with shared data in the multithreaded environments.

Example

The following program demonstrates the implementation of the above concept in C++.

C++
// C++ Program for using volatile variables in classes

#include <chrono>
#include <iostream>
#include <thread>
using namespace std;

class SensorInterface {
private:
    volatile int temperature;

public:
    SensorInterface()
        : temperature(0)
    {
    }

    void readSensor()
    {
        // Simulate reading from a sensor
        while (true) {
            temperature = rand() % 100;
            this_thread::sleep_for(chrono::seconds(1));
        }
    }

    int getTemperature() const { return temperature; }
};

int main()
{
    SensorInterface sensor;
    thread sensorThread(&SensorInterface::readSensor,
                        &sensor);

    for (int i = 0; i < 5; ++i) {
        cout << "Current temperature: "
             << sensor.getTemperature() << "°C" << endl;
        this_thread::sleep_for(chrono::seconds(1));
    }

    sensorThread.detach();
    return 0;
}


Output

Current temperature: 86°C
Current temperature: 77°C
Current temperature: 15°C
Current temperature: 93°C

Explanation: In the above program the temperature member variable of the class sensorInterface is declared volatile because it is updated in a separate thread. The volatile keyword will ensure that everytime getTemperature() function is called, it reads the latest value from the memory rather than using a cached value stored by the compiler for optimizations.

Volatile Pointers

Just like variables the value represented by the pointers can also change unexpectedly or there can be scenarios where the pointers points to a volatile data. In such scenarios, it is recommended to use volatile pointers so that the pointers always points to the most recent value of the data. Following is the syntax to use volatile pointers in C++.

Syntax to Define Volatile Pointers

There are 3 ways to declare volatile pointers in C++:

1. Pointer to volatile variable

volatile data_type* ptr = & volatile_variable.

2. Volatile pointer to non volatile data

data_type* volatile = &variable_name

3. Volatile pointer to volatile data

volatile data_type* volatile ptr = &volatile_variable

Here,

  • data_type: It represents the type of the data pointed by the pointer.
  • variable_name: It is the name of non volatile variable.
  • volatile_variable: It is the name of the volatile vairable.

Example

The following program demonstrates the implementation of the above concept in C++.

C++
// C++ Program for using volatile pointes

#include <iostream>
using namespace std;

int main()
{
    int regular_var = 10;
    volatile int volatile_var = 20;

    // Pointer to volatile data
    volatile int* ptr_to_volatile = &volatile_var;

    // Volatile pointer to non-volatile data
    int* volatile volatile_ptr = &regular_var;

    // Volatile pointer to volatile data
    volatile int* volatile volatile_ptr_to_volatile
        = &volatile_var;

    // Usage
    *ptr_to_volatile = 30;
    cout << "volatile_var: " << volatile_var << endl;

    *volatile_ptr = 40;
    cout << "regular_var: " << regular_var << endl;

    *volatile_ptr_to_volatile = 50;
    cout << "volatile_var: " << volatile_var << endl;

    return 0;
}

Output
volatile_var: 30
regular_var: 40
volatile_var: 50

Explanation: The following program demonstrates the three ways of using volatile pointers. ptr_to_volatile ensures that the data it points to is always read from memory. volatile_ptr ensures that the pointer itself is always read from memory. volatile_ptr_to_volatile combines both effects, ensuring that both the pointer and the data it points to are always read from memory and gets the most recent value not the cached value stored by the compiler for optimization.

Volatile Member Functions

We can also declare the member functions within a class as volatile so that the compiler does not optimize the function calls away. The volatile member functions indicates that the function may be called on volatile objects and doesn't modify the object state.

Example

The following program demonstrates the implementation of the above concept in C++.

C++
// C++ Program for using volatile member functions
#include <chrono>
#include <iostream>
#include <thread>
using namespace std;

class SafeCounter {
private:
    volatile int count;

public:
    SafeCounter()
        : count(0)
    {
    }

    void increment() volatile { ++count; }

    int getValue() const volatile { return count; }
};

void incrementer(SafeCounter& counter)
{
    for (int i = 0; i < 1000000; ++i) {
        counter.increment();
    }
}

int main()
{
    SafeCounter counter;
    thread t1(incrementer, ref(counter));
    thread t2(incrementer, ref(counter));

    t1.join();
    t2.join();

    cout << "Final count: " << counter.getValue() << endl;

    return 0;
}


Output

Final count: 2000000

Explanation: In the above example, the increment() and the getvalue() functions are declared as volatile member functions. This allows them to be called on volatile objects from the safeCounter class. The volatile qualifier on these functions ensures that they always work with the most up-to-date value of count, even in a multithreaded environment.

Volatile Qualifier in Multithreaded Environments

In a multithreaded environment when multiple threads access a shared variable concurrently, there's a potential risk of race conditions and data inconsistencies. By declaring shared variables to be volatile, we can ensure that each thread reads the most recent value from memory and writes it from other threads immediately.

Example

The following program demonstrates the use of volatile qualifier in a multithreaded environment.

C++
// C++ Program using Volatile Qualifier in Multithreaded
// Environments
#include <chrono>
#include <iostream>
#include <thread>
using namespace std;

// Declare volatile variable
volatile bool data_ready = false;

void producer()
{
    // Simulate data production
    this_thread::sleep_for(chrono::seconds(1));
    cout << "Producer: Data produced." << endl;

    // Signal data is ready
    data_ready = true;
}

void consumer()
{
    // Wait for data to become ready
    while (!data_ready) {
        // Spin-wait until data is ready (not recommended in
        // real scenarios)
    }
    // Once data is ready, consume it
    cout << "Consumer: Data consumed." << endl;
}

int main()
{
    // Create threads
    thread t1(producer);
    thread t2(consumer);

    // Join threads
    t1.join();
    t2.join();

    // Print completion message
    cout << "Main: All threads have finished execution."
         << endl;

    return 0;
}


Output

Producer: Data produced.
Consumer: Data consumed.
Main: All threads have finished execution.

Time Complexity: O(1)
Auxiliary Space: O(1)

Explanation: The above program is a solution of classical synchronization problem known as the producer consumer problem. In this program, volatile qualifier bool data_ready ensures that the consumer thread reliably observes changes made by the producer thread to data_ready variable. This prevents compiler optimizations that could lead to stale reads, highlighting volatile's crucial role in maintaining synchronization and correct behavior in multithreaded environments.

Applications of Volatile Qualifier in C++

Following are some common applications of the volatile qualifier:

  • Real-time Programming: volatile qualifier is used to ensure immediate response to asynchronous external events without compiler optimizations in real time programming.
  • Hardware Interfacing: volatile ensures correct access to hardware registers and memory-mapped I/O in embedded systems.
  • Multithreaded Environments: volatile helps to ensure the visibility of shared variables across threads, although additional synchronization mechanisms are often necessary.Asynchronous Event Flags: volatile flags indicate state changes from asynchronous events, ensuring the program reacts correctly.
  • Memory-Mapped Structures: volatile qualfier guarantees correct access to shared data structures across different execution contexts or processes.

Next Article
Practice Tags :

Similar Reads