C++ 中的 volatile 限定符

Jinku Hu 2021年6月28日
C++ 中的 volatile 限定符

本文将介绍 C++ 中的 volatile 限定符。

在 C++ 中使用 volatile 限定符来表示被另一个线程或外部操作修改的对象

volatile 关键字行为通常应被视为依赖于硬件,并且用户空间应用程序开发人员应始终查阅特定的编译器手册,了解如何在各种情况下解释限定符。通常,volatile 关键字通知编译器给定的对象不应该针对加载操作进行优化,并且总是从主内存而不是寄存器或缓存中检索。请注意,有多个缓存层次结构,软件通常无法访问这些层次结构,仅在硬件中进行管理,但是当编译器尝试加载寄存器中的内存位置时,它会自动被缓存。因此,对同一内存位置的后续访问可以从靠近 CPU 的高速缓存行完成,并且比 RAM 快数倍。

同时,如果对象被某些外部信号或类似中断的例程修改,则应从 RAM 访问更改的值,因为缓存的值不再有效。因此,对 volatile 对象的访问由编译器相应地处理。为了演示可能的场景,我们实现了一个修改全局 volatile 整数变量的函数和另一个在 while 循环语句中计算相同整数的函数。注意 while 循环可以有一个空的主体来让这个例子工作。首先,main 函数创建一个单独的线程来执行 IncrementSeconds 函数。紧接着,主线程调用 DelayTenSeconds 函数,该函数进入循环,如果 seconds 变量不超过 10 的值,该循环不会返回。由于另一个线程已经同时开始增加 seconds 变量,主线程将很快观察到更改后的值并从函数返回。

#include <iostream>
#include <unistd.h>
#include <thread>

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

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

输出:

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

因此,我们基本上实现了一个条件延迟函数,它会等待 volatile 对象被外部操作修改。现在,人们可能会注意到,如果删除 volatile 限定符并使用常规全局变量,则此代码的行为将完全相同,但不应忘记修改代码块可能来自不同的翻译单元或外部信号操作。后一种情况将迫使编译器优化 DelayTenSeconds 循环,因为当前翻译单元中未修改该变量。

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

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

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