C/C++ 中的 volatile 關(guān)鍵字和 const 對(duì)應(yīng),用來(lái)修飾變量,通常用于建立語(yǔ)言級(jí)別的 memory barrier。這是 BS 在 "The C++ Programming Language" 對(duì) volatile 修飾詞的說(shuō)明:
A volatile specifier is a hint to a compiler that an object may change its value in ways not specified by the language so that aggressive optimizations must be avoided.
volatile 關(guān)鍵字是一種類型修飾符,用它聲明的類型變量表示可以被某些編譯器未知的因素更改,比如:操作系統(tǒng)、硬件或者其它線程等。遇到這個(gè)關(guān)鍵字聲明的變量,編譯器對(duì)訪問(wèn)該變量的代碼就不再進(jìn)行優(yōu)化,從而可以提供對(duì)特殊地址的穩(wěn)定訪問(wèn)。聲明時(shí)語(yǔ)法:int volatile vInt; 當(dāng)要求使用 volatile 聲明的變量的值的時(shí)候,系統(tǒng)總是重新從它所在的內(nèi)存讀取數(shù)據(jù),即使它前面的指令剛剛從該處讀取過(guò)數(shù)據(jù)。而且讀取的數(shù)據(jù)立刻被保存。例如:
1 volatile int i=10;
2 int a = i;
3 ...
4 // 其他代碼,并未明確告訴編譯器,對(duì) i 進(jìn)行過(guò)操作
5 int b = i;
volatile 指出 i 是隨時(shí)可能發(fā)生變化的,每次使用它的時(shí)候必須從 i的地址中讀取,因而編譯器生成的匯編代碼會(huì)重新從i的地址讀取數(shù)據(jù)放在 b 中。而優(yōu)化做法是,由于編譯器發(fā)現(xiàn)兩次從 i讀數(shù)據(jù)的代碼之間的代碼沒(méi)有對(duì) i 進(jìn)行過(guò)操作,它會(huì)自動(dòng)把上次讀的數(shù)據(jù)放在 b 中。而不是重新從 i 里面讀。這樣以來(lái),如果 i是一個(gè)寄存器變量或者表示一個(gè)端口數(shù)據(jù)就容易出錯(cuò),所以說(shuō) volatile 可以保證對(duì)特殊地址的穩(wěn)定訪問(wèn)。注意,在 VC 6 中,一般調(diào)試模式?jīng)]有進(jìn)行代碼優(yōu)化,所以這個(gè)關(guān)鍵字的作用看不出來(lái)。下面通過(guò)插入?yún)R編代碼,測(cè)試有無(wú) volatile 關(guān)鍵字,對(duì)程序最終代碼的影響:
輸入下面的代碼:
01 #include <stdio.h>
02
03 void main()
04 {
05 int i = 10;
06 int a = i;
07
08 printf("i = %d", a);
09
10 // 下面匯編語(yǔ)句的作用就是改變內(nèi)存中 i 的值
11 // 但是又不讓編譯器知道
12 __asm {
13 mov dword ptr [ebp-4], 20h
14 }
15
16 int b = i;
17 printf("i = %d", b);
18 }
然后,在 Debug 版本模式運(yùn)行程序,輸出結(jié)果如下:
i = 10
i = 32
然后,在 Release 版本模式運(yùn)行程序,輸出結(jié)果如下:
i = 10
i = 10
輸出的結(jié)果明顯表明,Release 模式下,編譯器對(duì)代碼進(jìn)行了優(yōu)化,第二次沒(méi)有輸出正確的 i 值。下面,我們把 i 的聲明加上 volatile 關(guān)鍵字,看看有什么變化:
01 #include <stdio.h>
02
03 void main()
04 {
05 volatile int i = 10;
06 int a = i;
07
08 printf("i = %d", a);
09 __asm {
10 mov dword ptr [ebp-4], 20h
11 }
12
13 int b = i;
14 printf("i = %d", b);
15 }
分別在 Debug 和 Release 版本運(yùn)行程序,輸出都是:
i = 10
i = 32
這說(shuō)明這個(gè) volatile 關(guān)鍵字發(fā)揮了它的作用。其實(shí)不只是“內(nèi)嵌匯編操縱棧”這種方式屬于編譯無(wú)法識(shí)別的變量改變,另外更多的可能是多線程并發(fā)訪問(wèn)共享變量時(shí),一個(gè)線程改變了變量的值,怎樣讓改變后的值對(duì)其它線程 visible。一般說(shuō)來(lái),volatile用在如下的幾個(gè)地方:
1) 中斷服務(wù)程序中修改的供其它程序檢測(cè)的變量需要加volatile;
2) 多任務(wù)環(huán)境下各任務(wù)間共享的標(biāo)志應(yīng)該加volatile;
3) 存儲(chǔ)器映射的硬件寄存器通常也要加volatile說(shuō)明,因?yàn)槊看螌?duì)它的讀寫都可能由不同意義;
2.volatile 指針
和 const 修飾詞類似,const 有常量指針和指針常量的說(shuō)法,volatile 也有相應(yīng)的概念:
修飾由指針指向的對(duì)象、數(shù)據(jù)是 const 或 volatile 的:
1 const char* cpch;
2 volatile char* vpch;
注意:對(duì)于 VC,這個(gè)特性實(shí)現(xiàn)在 VC 8 之后才是安全的。
指針自身的值——一個(gè)代表地址的整數(shù)變量,是 const 或 volatile 的:
1 char* const pchc;
2 char* volatile pchv;
注意:(1) 可以把一個(gè)非volatile int賦給volatile int,但是不能把非volatile對(duì)象賦給一個(gè)volatile對(duì)象。
(2) 除了基本類型外,對(duì)用戶定義類型也可以用volatile類型進(jìn)行修飾。
(3) C++中一個(gè)有volatile標(biāo)識(shí)符的類只能訪問(wèn)它接口的子集,一個(gè)由類的實(shí)現(xiàn)者控制的子集。用戶只能用const_cast來(lái)獲得對(duì)類型接口的完全訪問(wèn)。此外,volatile向const一樣會(huì)從類傳遞到它的成員。
3. 多線程下的volatile
有些變量是用volatile關(guān)鍵字聲明的。當(dāng)兩個(gè)線程都要用到某一個(gè)變量且該變量的值會(huì)被改變時(shí),應(yīng)該用volatile聲明,該關(guān)鍵字的作用是防止優(yōu)化編譯器把變量從內(nèi)存裝入CPU寄存器中。如果變量被裝入寄存器,那么兩個(gè)線程有可能一個(gè)使用內(nèi)存中的變量,一個(gè)使用寄存器中的變量,這會(huì)造成程序的錯(cuò)誤執(zhí)行。volatile的意思是讓編譯器每次操作該變量時(shí)一定要從內(nèi)存中真正取出,而不是使用已經(jīng)存在寄存器中的值,如下:
volatile BOOL bStop = FALSE;
(1) 在一個(gè)線程中:
while( !bStop ) { ... }
bStop = FALSE;
return;
(2) 在另外一個(gè)線程中,要終止上面的線程循環(huán):
bStop = TRUE;
while( bStop ); //等待上面的線程終止,如果bStop不使用volatile申明,那么這個(gè)循環(huán)將是一個(gè)死循環(huán),因?yàn)閎Stop已經(jīng)讀取到了寄存器中,寄存器中bStop的值永遠(yuǎn)不會(huì)變成FALSE,加上volatile,程序在執(zhí)行時(shí),每次均從內(nèi)存中讀出bStop的值,就不會(huì)死循環(huán)了。
這個(gè)關(guān)鍵字是用來(lái)設(shè)定某個(gè)對(duì)象的存儲(chǔ)位置在內(nèi)存中,而不是寄存器中。因?yàn)橐话愕膶?duì)象編譯器可能會(huì)將其的拷貝放在寄存器中用以加快指令的執(zhí)行速度,例如下段代碼中:
...
int nMyCounter = 0;
for(; nMyCounter<100;nMyCounter++)
{
...
}
...
在此段代碼中,nMyCounter的拷貝可能存放到某個(gè)寄存器中(循環(huán)中,對(duì)nMyCounter的測(cè)試及操作總是對(duì)此寄存器中的值進(jìn)行),但是另外又有段代碼執(zhí)行了這樣的操作:nMyCounter -= 1;這個(gè)操作中,對(duì)nMyCounter的改變是對(duì)內(nèi)存中的nMyCounter進(jìn)行操作,于是出現(xiàn)了這樣一個(gè)現(xiàn)象:nMyCounter的改變不同步。