什麼是volatile(型別修飾詞)

C語言的型別修飾詞之一,告訴編譯器該變數的值可能在程式看不到的地方(周邊電路、中斷處理常式等)改變,或讀寫本身具有意義。對加上 volatile 之物件的存取不會被省略或合併,會依撰寫的次數與順序執行。用於記憶體映射I/O的暫存器,以及與中斷處理常式共用的旗標。它沒有讓操作不可分割,或保證與其他核心之間順序的作用。

日文名稱: volatile(型修飾子)

「volatile(型別修飾詞)」出現在6領域的7道練習題中(C語言基礎, 作業系統與硬體, 多核心與同步等)。

這個名詞所屬的領域: C語言基礎

不加會發生什麼

在 while (flag == 0) { } 這類迴圈中,若只有中斷處理常式會改變 flag,編譯器可能判斷 flag 在迴圈中不會改變,只讀一次而變成無窮迴圈。等待周邊電路狀態暫存器的迴圈也會發生同樣的事,連續寫入同一暫存器的處理也可能被合併成一次。加上 volatile 後,就會依程式碼所寫的次數實際讀寫。

宣告的讀法

volatile uint32_t *p 是「指向 volatile uint32_t 的指標」,對 *p 的存取不會被省略。寫成 volatile uint32_t *const p,則再加上指標本身不能改變(永遠指向同一個暫存器)的意思。像 #define REG (*(volatile uint32_t *)0x40020014) 這種把位址轉換成指標再參考的巨集也很常用。

volatile 做不到的事

volatile 不會讓讀取、修改、寫入的操作不可分割,因此在 *reg |= mask; 途中發生中斷時,另一個位元的變更可能消失。像8位元微控制器上的32位元變數這種無法一次讀寫的值,若途中被中斷,可能只讀到部分更新的值。執行緒之間的同步要使用 C11 的 _Atomic 或鎖等保證不可分割性與順序的機制。

練習volatile(型別修飾詞)的題目

在C語言中,僅對執行緒之間傳遞的旗標變數加上volatile,仍不足以作為同步,其理由下列何者正確?

  1. A因為加了volatile的變數,其他執行緒就無法讀取
  2. B因為volatile只是抑制編譯器的最佳化,並不保證操作的不可分割性,也不保證其他核心看到的順序
  3. C因為加上volatile後,變數會被存到磁碟而變慢
查看答案與解析

正確答案:B. 因為volatile只是抑制編譯器的最佳化,並不保證操作的不可分割性,也不保證其他核心看到的順序

volatile是指示編譯器不要省略或合併變數的讀寫,原本是為了硬體暫存器的操作而設計的。它無法防止CPU調換記憶體操作的順序,也不會讓counter++這類操作變得不可分割。C11與C++11以後,只要使用_Atomic或std::atomic,就能同時保證不可分割性與記憶體順序。

常見問題

const 與 volatile 可以同時加上嗎?

可以。const volatile 表示「程式不會改寫,但會因外部因素而改變,所以每次都要重新讀取」,用於唯讀的狀態暫存器等。

加上 volatile 會變慢嗎?

就該變數而言,無法再進行把值保留在暫存器中的最佳化,因此存取增加,可能變慢。所以不是對所有變數都加上,而是只加在周邊電路的暫存器、與中斷處理常式共用的變數等需要的地方。

解析中的「volatile(型別修飾詞)」

出現「volatile(型別修飾詞)」的題目(7題)

  1. 如上方所示,以記憶體映射I/O(memory-mapped I/O)讀取裝置的狀態暫存器時,加上volatile並將該區域設為不快取,其理由為下列何者?
  2. 在C語言中,僅對執行緒之間傳遞的旗標變數加上volatile,仍不足以作為同步,其理由下列何者正確?
  3. 在微控制器等中,將周邊裝置的暫存器對應到與記憶體相同的位址空間,並以一般的記憶體讀寫指令來操作的方式是下列何者?
  4. 將上面的程式啟用最佳化進行編譯後,即使發生計時器中斷,有時也不會呼叫led_on()。原因與對策的組合,下列何者正確?
  5. 在嵌入式程式中,存放32位元周邊暫存器值的變數,常以 stdint.h 的 uint32_t 宣告,而不是 unsigned int。主要理由為下列何者?
  6. 微控制器的輸出暫存器以上方的方式宣告並使用。關於這個宣告的說明,下列何者正確?
  7. 以上述的小迴圈測量運算速度,結果即使加大N,時間也幾乎是0。最可能的原因為下列何者?

相關名詞

記憶體對映I/O 變數 微控制器(MCU) 中斷處理常式 UART 中斷 指標(pointer) 微基準測試(Microbenchmark) 巨集(macro) 未定義行為

前往名詞解釋列表