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