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