volatile 是“易變的”、“不穩定”的意思。volatile是 c++ 的一個關鍵字,用來解決在“共享”環境下容易出現的讀取錯誤的問題。
在單任務的環境中,一個函數體內部,如果在兩次讀取變量的值之間的語句沒有對變量的值進行修改,那麼編譯器就會設法對可執行代碼進行優化。由於訪問寄存器的速度要快過RAM(從RAM中讀取變量的值到寄存器),以後只要變量的值沒有改變,就一直從寄存器中讀取變量的值,而不對RAM進行訪問。
這雖然在單任務環境下是一個優化過程,但是卻是多任務環境下問題的起因。
多任務環境中,雖然在一個函數體內部,在兩次讀取變量之間沒有對變量的值進行修改,但是該變量仍然有可能被其他的程序(如中斷程序、另外的線程等)所修改。如果還是從寄存器而不是從RAM中讀取變量的值,就會出現被修改了的比阿郎的之不能及時的反應的問題。如下程序對這一現象進行了模擬:
#include
using namespace std;
int main(int argc,char* argv[])
{
int i=10;
int a=i;
cout<
程序在VS2012環境下生成 release 版本(一定要極端優化,vs編譯環境下選擇優化 速度最大化 /O2),輸出結果也是:
10
10
順便說一下,ebp是擴展基址指針寄存器(extended base pointer) 其內存放一個指針,該指針指向系統棧最上面一個棧幀的底部。
本來事實上已經通過內聯匯編,修改過的值,為什麼打印出來還是10呢
但是如果:
將 int i=10; 前加 volatile 就不會發生這種情況了。
跟蹤匯編代碼可以發現,凡是聲明為 volatile 的變量,每次拿到的值都是從內存中直接讀取的。
以下實驗在 vs2012 release 環境下進行。
不加 volatile
int i=10;
int a=i;
tmp(a);
00D71273 push dword ptr ds:[0D73024h]
00D71279 mov ecx,dword ptr ds:[0D7303Ch]
00D7127F push 0Ah
00D71281 call dword ptr ds:[0D7302Ch]
00D71287 mov ecx,eax
00D71289 call dword ptr ds:[0D73028h]
_asm{
mov dword ptr [ebp-4],80
00D7128F mov dword ptr [ebp-4],50h
}
int b=i;
tmp(b);
00D71296 push dword ptr ds:[0D73024h]
00D7129C mov ecx,dword ptr ds:[0D7303Ch]
00D712A2 push 0Ah
00D712A4 call dword ptr ds:[0D7302Ch]
00D712AA mov ecx,eax
00D712AC call dword ptr ds:[0D73028h]
加了 volatile
tmp(a);
01201274 push dword ptr ds:[1203024h]
volatile int i=10;
0120127A mov dword ptr [i],0Ah
int a=i;
01201281 mov eax,dword ptr [i]
tmp(a);
01201284 mov ecx,dword ptr ds:[120303Ch]
0120128A push eax
0120128B call dword ptr ds:[120302Ch]
01201291 mov ecx,eax
01201293 call dword ptr ds:[1203028h]
_asm{
mov dword ptr [ebp-4],80
01201299 mov dword ptr [i],50h
}
int b=i;
012012A0 mov eax,dword ptr [i]
tmp(b);
012012A3 push dword ptr ds:[1203024h]
012012A9 mov ecx,dword ptr ds:[120303Ch]
012012AF push eax
tmp(b);
012012B0 call dword ptr ds:[120302Ch]
012012B6 mov ecx,eax
012012B8 call dword ptr ds:[1203028h]
由於編譯器的極端優化,可以很明顯的看到,在沒有加 volatile 的情況下,甚至編譯器是直接使用操作數 0Ah 進行運算的。
而在加了 volatile 的情況下,每次都是從 ptr [i] 中讀取。
而且在速度極端優化的情況下,
void tmp(int t) {
cout<
也自動 inline 處理了。
但是這裡也拋出一個問題,為什麼是 [ebp-4] 修改的就是i的值,更奇怪的是,我如果如下這樣寫代碼,那改的會是哪個變量的值呢:
#include
using namespace std;
void tmp(int t) {
cout<
為什麼分配的總是 [ebp-4] 是復制給 a 的值呢?試驗過,如果將 ic 賦值給 a,那 [ebp-4] 存放的值將會是 ic
閱讀以上程序,注意以下幾個要點:
(1)以上代碼必須在release模式下考查,因為只有Release模式(嚴格說需要在速度最大優化 /O2)下才會對程序代碼進行優化,而這種優化在變量共享的環境下容易引發問題。
(2)凡是需要被多個任務共享的變量(如可能被中斷服務程序訪問的變量、被其他線程訪問的變量等),都應聲明為 volatile 變量。而且為了提高執行效率,要減少對 volatile 不必要的使用。
(3)由於優化可能會將一些“無用”的代碼徹底去除,所以,如果確實希望在可執行文件中保留這部分代碼,也可以將其中的變量聲明為 volatile:
int main(int argc,char* argv[])
{
int s,i,j;
for(i=0;i<100;++i)
for(j=0;j<100;++j)
s=5;
return 0;
}
在生成 release 版本的程序時,由於循環體每次給 s 的值不變(簡化為執行1次),或者說沒有使用(1次都沒有),但如果此時程序猿是希望循環拖延時間,寫成 volatile 就可以了。
附錄:問題
1)一個參數既可以是const還可以是volatile嗎?解釋為什麼
是的。一個例子是只讀的狀態寄存器。它是volatile因為它可能被意想不到地改變。它是const因為程序不應該試圖去修改它。
2) 一個指針可以是volatile 嗎?解釋為什麼
是的。盡管這並不很常見。一個例子是當一個中斷服務子程序修該一個指向一個buffer的指針時。
3) 下面的函數有什麼錯誤:
int square(volatile int *ptr) {
return *ptr * *ptr;
}
這段代碼的目的是用來返指針*ptr指向值的平方,但是,由於*ptr指向一個volatile型參數,編譯器將產生類似下面的代碼:
int square(volatile int *ptr) {
int a,b;
a = *ptr;
b = *ptr;
return a * b;
}
由於*ptr的值可能被意想不到地該變,因此a和b可能是不同的。結果,這段代碼可能返不是
你所期望的平方值!正確的代碼如下:
long square(volatile int *ptr) {
int a;
a = *ptr;
return a * a;
}