名稱空間
變體
操作

volatile 型別限定符

來自 cppreference.com
< c‎ | 語言

C 型別系統中的每個單獨型別都有其幾種限定版本,對應於一個、兩個或全部三個限定符:constvolatile,以及對於指向物件型別的指標,restrict。本頁描述了 volatile 限定符的效果。

透過 volatile 限定型別左值表示式進行的每次訪問(讀和寫)都被視為最佳化的可觀察副作用,並嚴格按照抽象機器的規則進行評估(即,所有寫入都在下一個序列點之前的某個時間完成)。這意味著在單個執行執行緒中,volatile 訪問不能被最佳化掉,也不能相對於與 volatile 訪問透過序列點分離的另一個可見副作用進行重新排序。

將非 volatile 值強制轉換為 volatile 型別沒有效果。要使用 volatile 語義訪問非 volatile 物件,必須將其地址強制轉換為指向 volatile 的指標,然後透過該指標進行訪問。

任何透過非 volatile 左值讀寫 volatile 限定型別的物件的嘗試都會導致未定義行為。

volatile int n = 1; // object of volatile-qualified type
int* p = (int*)&n;
int val = *p; // undefined behavior

volatile 限定結構或聯合型別的成員會獲得其所屬型別的限定(無論是使用 . 運算子還是 -> 運算子訪問)。

struct s { int i; const int ci; } s;
// the type of s.i is int, the type of s.ci is const int
volatile struct s vs;
// the types of vs.i and vs.ci are volatile int and const volatile int

如果陣列型別透過使用 typedef 宣告為 volatile 型別限定符,則陣列型別不是 volatile 限定的,但其元素型別是。

(直至 C23)

陣列型別及其元素型別始終被認為是具有相同 volatile 限定的。

(自 C23 起)
typedef int A[2][3];
volatile A a = {{4, 5, 6}, {7, 8, 9}}; // array of array of volatile int
int* pi = a[0]; // Error: a[0] has type volatile int*
void *unqual_ptr = a; // OK until C23; error since C23
// Notes: clang applies the rule in C++/C23 even in C89-C17 modes

如果函式型別透過使用 typedef 宣告為 volatile 型別限定符,則行為是未定義的。

在函式宣告中,關鍵字 volatile 可以出現在用於宣告函式引數陣列型別的方括號內。它限定了陣列型別轉換成的指標型別。

以下兩個宣告聲明瞭相同的函式

void f(double x[volatile], const double y[volatile]);
void f(double * volatile x, const double * volatile y);
(C99 起)

指向非 volatile 型別的指標可以隱式轉換為指向相同或相容型別的 volatile 限定版本的指標。反向轉換需要強制轉換表示式。

int* p = 0;
volatile int* vp = p; // OK: adds qualifiers (int to volatile int)
p = vp; // Error: discards qualifiers (volatile int to int)
p = (int*)vp; // OK: cast

注意,指向 T 的指標不能轉換為指向 volatile T 的指標;對於兩種型別要相容,它們的限定必須相同。

char *p = 0;
volatile char **vpp = &p; // Error: char* and volatile char* are not compatible types
char * volatile *pvp = &p; // OK, adds qualifiers (char* to char*volatile)

目錄

[編輯] volatile 的用途

1) static volatile 物件模擬記憶體對映 I/O 埠,而 static const volatile 物件模擬記憶體對映輸入埠,例如即時時鐘。
volatile short *ttyport = (volatile short*)TTYPORT_ADDR;
for(int i = 0; i < N; ++i)
    *ttyport = a[i]; // *ttyport is an lvalue of type volatile short
2) 型別為 sig_atomic_tstatic volatile 物件用於與訊號處理程式通訊。
3) 包含 setjmp 宏呼叫的函式中的區域性 volatile 變數是唯一保證在 longjmp 返回後保留其值的區域性變數。
4) 此外,volatile 變數可用於停用某些形式的最佳化,例如,停用微基準測試中的死儲存消除或常量摺疊。

注意,volatile 變數不適合執行緒間通訊;它們不提供原子性、同步或記憶體排序。在沒有同步的情況下,從一個被另一個執行緒修改的 volatile 變數中讀取,或者來自兩個未同步執行緒的併發修改,由於資料競爭而導致未定義行為。

[編輯] 關鍵詞

volatile

[編輯] 示例

演示了使用 volatile 停用最佳化

#include <stdio.h>
#include <time.h>
 
int main(void)
{
    clock_t t = clock();
    double d = 0.0;
    for (int n = 0; n < 10000; ++n)
        for (int m = 0; m < 10000; ++m)
            d += d * n * m; // reads from and writes to a non-volatile 
    printf("Modified a non-volatile variable 100m times. "
           "Time used: %.2f seconds\n",
           (double)(clock() - t)/CLOCKS_PER_SEC);
 
    t = clock();
    volatile double vd = 0.0;
    for (int n = 0; n < 10000; ++n)
        for (int m = 0; m < 10000; ++m) {
            double prod = vd * n * m; // reads from a volatile
            vd += prod; // reads from and writes to a volatile
        } 
    printf("Modified a volatile variable 100m times. "
           "Time used: %.2f seconds\n",
           (double)(clock() - t)/CLOCKS_PER_SEC);
}

可能的輸出

Modified a non-volatile variable 100m times. Time used: 0.00 seconds
Modified a volatile variable 100m times. Time used: 0.79 seconds

[編輯] 參考文獻

  • C17 標準 (ISO/IEC 9899:2018)
  • 6.7.3 型別限定符 (p: 87-90)
  • C11 標準 (ISO/IEC 9899:2011)
  • 6.7.3 型別限定符 (p: 121-123)
  • C99 標準 (ISO/IEC 9899:1999)
  • 6.7.3 型別限定符 (p: 108-110)
  • C89/C90 標準 (ISO/IEC 9899:1990)
  • 6.5.3 型別限定符

[編輯] 另請參閱

C++ 文件,關於cv (constvolatile) 型別限定符