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