C++ 中的 volatile 限定符

Jinku Hu 2023年10月12日
C++ 中的 volatile 限定符

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

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

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

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

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

输出:

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

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

#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;
}
作者: Jinku Hu
Jinku Hu avatar Jinku Hu avatar

DelftStack.com 创始人。Jinku 在机器人和汽车行业工作了8多年。他在自动测试、远程测试及从耐久性测试中创建报告时磨练了自己的编程技能。他拥有电气/电子工程背景,但他也扩展了自己的兴趣到嵌入式电子、嵌入式编程以及前端和后端编程。

LinkedIn Facebook