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;
}
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