The volatile Qualifier in C++

Jinku Hu Oct 12, 2023
The volatile Qualifier in C++

This article will introduce the volatile qualifier in C++.

Use the volatile Qualifier to Denote the Object That Gets Modified by Another Thread or External Action in C++

The volatile keyword behavior should generally be considered as hardware-dependent, and user-space application developers should always consult the specific compiler manuals on how the qualifier may get interpreted in various scenarios. Usually, the volatile keyword notifies the compiler that the given object should not be optimized for load operations and always retrieved from the main memory instead of the registers or caches. Notice that there are multiple hierarchies of caches that are mostly inaccessible to the software and managed solely in the hardware, but when the compiler tries to load the memory location in the register, it automatically gets cached. Thus, consequent access to the same memory location can be done from the cache lines close to the CPU and multiple times faster than RAM.

Meanwhile, if the object is modified by some external signal or an interrupt-like routine, the changed value should be accessed from the RAM as the cached value is no longer valid. Thus, accesses to volatile objects are handled by the compiler correspondingly. To demonstrate the possible scenario, we implement a function that modifies the global volatile integer variable and another function that evaluates the same integer in the while loop statement. Notice that the while loop can have an empty body for this example to work. At first, the main function creates a separate thread that executes the IncrementSeconds function. Right after that, the main thread invokes the DelayTenSeconds function, which goes into the loop that won’t return if the seconds variable does not exceed the value of 10. Since the other thread started incrementing the seconds variable already concurrently, the main thread will soon observe the changed value and return from the function.

#include <unistd.h>

#include <iostream>
#include <thread>

using std::cerr;
using std::cin;
using std::cout;
using std::endl;

volatile int seconds = 0;

void DelayTenSeconds() {
  while (seconds < 10) {
    usleep(500000);
    cerr << "waiting..." << endl;
  }
}

void IncrementSeconds() {
  for (int i = 0; i < 10; ++i) {
    sleep(1);
    cerr << "incremented " << endl;
    seconds = seconds + 1;
  }
}

int main() {
  struct timeval start {};
  struct timeval end {};
  std::thread th1;

  th1 = std::thread(IncrementSeconds);

  DelayTenSeconds();

  th1.join();
  return EXIT_SUCCESS;
}

Output:

waiting...
incremented
waiting...
....
waiting...
10.002481 sec

As a result, we essentially implemented a conditional delay function that waits until the volatile object is modified by external action. Now, one might notice that this code will behave exactly the same if the volatile qualifier is removed and regular global variable is utilized, but one should not forget that modification code block may be from a different translation unit or external signal action. The latter scenarios would force the compiler to optimize the DelayTenSeconds loop since the variable does not get modified in the current translation unit.

#include <sys/time.h>
#include <unistd.h>

#include <iostream>
#include <thread>

using std::cerr;
using std::cin;
using std::cout;
using std::endl;

volatile int seconds = 0;

void DelayTenSeconds() {
  while (seconds < 10) {
    usleep(500000);
    cerr << "waiting..." << endl;
  }
}

float TimeDiff(struct timeval *start, struct timeval *end) {
  return (end->tv_sec - start->tv_sec) + 1e-6 * (end->tv_usec - start->tv_usec);
}

void IncrementSeconds() {
  for (int i = 0; i < 10; ++i) {
    sleep(1);
    cerr << "incremented " << endl;
    seconds = seconds + 1;
  }
}

int main() {
  struct timeval start {};
  struct timeval end {};
  std::thread th1;

  th1 = std::thread(IncrementSeconds);

  gettimeofday(&start, nullptr);
  DelayTenSeconds();
  gettimeofday(&end, nullptr);

  printf("%0.6f sec\n", TimeDiff(&start, &end));

  th1.join();
  return EXIT_SUCCESS;
}
Author: Jinku Hu
Jinku Hu avatar Jinku Hu avatar

Founder of DelftStack.com. Jinku has worked in the robotics and automotive industries for over 8 years. He sharpened his coding skills when he needed to do the automatic testing, data collection from remote servers and report creation from the endurance test. He is from an electrical/electronics engineering background but has expanded his interest to embedded electronics, embedded programming and front-/back-end programming.

LinkedIn Facebook