volatile 的英文解釋是——“易失的,易改變的”。顧名思義,這個關鍵字的含義是向編譯器指明變量的內(nèi)容可能會由于編譯器意想不到的情況的變化而發(fā)生變化。這個解釋仍然比較抽象,感興趣的可以繼續(xù)閱讀下面內(nèi)容。
先看一下編譯器對程序的優(yōu)化過程是怎么進行的
如果編譯器在代碼中發(fā)現(xiàn)對同一地址的兩次訪問之間,沒有對該地址進行寫操作,那么編譯器的優(yōu)化過程認為——第一次尋址讀取該地址時取得的變量的值(編譯器會盡最大可能把這個值存放在通用寄存器R中或者cache中)作為第二次尋址的值,而并不是再做第二次內(nèi)存的 I/O 尋址操作(當CPU把該變量的值放到通用寄存器或者cache中中后就不會再關心對應內(nèi)存中的值)。
例如:
int i = 10;
int j = i; // (1)語句
int k = i; // (2)語句
因為在(1)、(2)兩條語句中,i沒有被用作左值(同一地址的兩次訪問之間,沒有對該地址進行寫操作),這時候編譯器認為i的值沒有發(fā)生變化,所以在(1)語句時,從內(nèi)存中取出i的值賦給j之后,這個值并沒有丟掉(存放在通用寄存器中),而是在(2)語句時繼續(xù)用這個值給k賦值。編譯器不會生成匯編代碼而重新從內(nèi)存里尋址i的值。
Volatile這個關鍵字的必要性(真正意義之所在)
但其他程序(例如內(nèi)核程序或一個中斷)修改了內(nèi)存中該變量的值,此時寄存器R中的值并不會隨之改變而更新,由于優(yōu)化器的作用編譯器仍然去利用之前存放在寄存器R中的值,而不去尋址內(nèi)存中的值(但我們必須改變這個變量的值)。為了解決這種情況C語言就引入了volatile限定詞,讓代碼在引用該變量時多費一點勁兒,再去內(nèi)存中取出該變量的值。
例如:
Volatile int i = 10;
int j = i; // (3)語句
int k = i; // (4)語句
這里,volatile關鍵字告訴編譯器i是隨時可能發(fā)生變化的,每次使用它的時候必須從內(nèi)存中取出i的值,因而編譯器生成的匯編代碼會重新從內(nèi)存中i的地址處讀取i的值存放在k中。
一句話概括就是,當用volatile關鍵字修飾變量時,優(yōu)化器在用到這個變量時必須每次都小心地去內(nèi)存重新讀取(關鍵之處)這個變量的值,而不是使用保存在寄存器R里的備份。
Volatile和register的對比
volatile 跟以前的 register 相反。 register 告訴編譯器盡量將變量放到寄存器中使用, 而volatile 強制將更改后的值寫回內(nèi)存。如果不寫回內(nèi)存,對于一些全局共享的變量,可能導致不一致問題。
volatie變量將和cache不發(fā)生關系,只和內(nèi)存有關系
簡單點說就是每次操作前從內(nèi)存取值
有volatie修飾的變量,每次操作時遵循下面動作:
從內(nèi)存取值 ---> 放入寄存器 ----> 操作 ----> 寫回內(nèi)存
沒有volatie修飾的變量,操作可能遵循:
從內(nèi)存取值 ---> 放入寄存器 ----> 第一次操作 -----> 第二次操作(此時仍操作寄存器中的值) …… ----> 第N次操作 ----> 寫回內(nèi)存
舉個例子論述兩者關系
int volatie i; //全局變量,在其它地方會被修改
while (i)
{
do_somethings();
}
如果i沒有被volatie修飾,當while循環(huán)執(zhí)行時,另一段程序并發(fā)的執(zhí)行了i = 0, 這個循環(huán)仍不會退出,因為每次循環(huán)都是檢查寄存器中的值。
如果有volatie修飾,那么循環(huán)結束,因為循環(huán)每次檢查i的時候,會先從內(nèi)存把i讀入寄存器,這個時候i在其它地方被賦0,則循環(huán)結束。
Volatile關鍵字應用的三個地方
1、 修改硬件寄存器,尤其是狀態(tài)寄存器
大家都知道,在硬件級別,如果寄存器值自動改變了,編譯器是不會主動發(fā)現(xiàn)的。經(jīng)過編譯器的自動優(yōu)化,我們讀到的都是寄存器中存儲的舊的狀態(tài)寄存器的值, 而非最新的狀態(tài)寄存器值。例如:
l #define STATUS (*(volatile unsigned long *)0x56000010)
2、 多線程中被幾個線程共享的變量
線程修改共享變量var是不會通知編譯器的。所以線程A堅持不懈地讀著var在寄存器或者cache中的副本,讀出來的內(nèi)容是0,但很可惜,線程B早就把var變量給修改為1了。鑒于此,我們必須加上volatile這個關鍵字來解決這個問題。
3、 中斷服務程序ISR當中用
ISR:中斷服務程序 (interrupt service routine)
所謂中斷是指當CPU正在處理某件事情的時候,外部發(fā)生的某一事件(如一個電平的變化,一個脈沖沿的發(fā)生或定時器計數(shù)溢出等)請求CPU迅速去處理,于是CPU暫時中止當前的工作,轉去處理所發(fā)生的事件。中斷服務處理完該事件以后,再回到原來被中止的地方繼續(xù)原來的工作。
由volatile引出的三個問題
1)一個參數(shù)既可以是const還可以是volatile嗎?解釋為什么。
2)一個指針可以是volatile 嗎?解釋為什么。
3)下面的函數(shù)有什么錯誤:
int square(volatile int *ptr)
{return *ptr * *ptr;}
答:
1)是的。一個典型的個例子就是只讀的狀態(tài)寄存器。
¨ 它是volatile因為它可能被意想不到地改變。
¨ 它是const因為程序不應該試圖去修改它。
2)是的。盡管這并不很常見。一個例子是當一個中服務子程序修改一個指向一個buffer的指針時。
3)這段代碼的有個惡作劇。這段代碼的目的是用來返指針*ptr指向值的平方,但是,由于*ptr指向一個volatile型參數(shù),
編譯器將產(chǎn)生類似下面的代碼:
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;
}