1. 程式人生 > >volatile 型別限定符——研讀C/C++標準

volatile 型別限定符——研讀C/C++標準

volatile 型別限定符

C 型別系統中每一個獨立的型別在都有數個該型別的限定版本,對應 const 、 volatile 及限定對於指向物件指標的 restrict 限定符中的一個、兩個或全部三個。此頁面描述 volatile 限定符的效果。

每一個通過對 volatile 限定型別左值表示式的訪問(讀與寫),對於優化意圖都被認為是可觀副效應,從而訪問會嚴格按照抽象機的規則求值(即所有寫入會在下一個序點之前的某時完成)。這表明在執行的單個執行緒內,volatile 訪問不能被優化掉,亦不能與另一個被順序點分隔了 volatile 訪問的可觀副效應重排。

一個非 volatile 值到 volatile 值的轉換是無效果的。欲使用 volatile 語義訪問非 volatile 物件,必須先將其地址轉換成指向 volatile 型別的指標,再通過該指標訪問該物件。

任何通過非 volatile 左值結果,對擁有 volatile 限定型別的物件嘗試讀或寫會導致未定義行為:

volatile int n = 1; // volatile 限定型別
int* p = (int*)&n;
int val = *p; // 未定義行為

volatile 限定的結構體或聯合體型別,其成員會獲取其所屬型別的限定(當通過 . 或 -> 運算子時):

struct s { int i; const int ci; } s;
// s.i 型別是 int,s.ci 的型別是 const int
volatile struct s vs;
// vs.i 和 vs.ci 的型別各是 volatile int 和 const volatile int

若陣列型別宣告具有 volatile 型別限定符(通過使用typedef),則陣列型別本身不是 volatile 限定的,但其元素型別是 volatile 限定。若函式型別宣告具有 volatile 型別限定(通過使用 typedef ),則行為未定義。

typedef int A[2][3];
volatile A a = {{4, 5, 6}, {7, 8, 9}}; // volatile int 的陣列的陣列
int* pi = a[0]; // 錯誤:a[0] 擁有 volatile int* 型別

在函式宣告中,關鍵詞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:新增限定符( int 到 volatile int)
p = vp; // 錯誤:丟棄限定符( volatile int 到 int)
p = (int*)vp; // OK:型別轉換

注意指向T的二重不可轉換成指向volatile T的二重指標;對於要相容的兩個型別,它們的限定必須相同:

char *p = 0;
volatile char **vpp = &p; // 錯誤: char* 和 volatile char* 不是相容型別
char * volatile *pvp = &p; // OK,新增限定符( char* 到 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 是 volatile short 型別的左值
  1. sig_atomic_t 型別的 static volatile 物件用於與 signal 處理交流。
  2. 含有對 setjmp 巨集呼叫的函式中的區域性 volatile 變數,是在 longjmp 返回後僅有保證恢復其值的區域性變數。
  3. 另外,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; // 讀寫非 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)
           vd += vd*n*m; // 讀寫 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

引用

  • C11 standard (ISO/IEC 9899:2011):
    6.7.3 Type qualifiers (p: 121-123)
  • C99 standard (ISO/IEC 9899:1999):
    6.7.3 Type qualifiers (p: 108-110)
  • C89/C90 standard (ISO/IEC 9899:1990):
    3.5.3 Type qualifiers