C語言volatile關鍵字
volatile 是易變的、不穩定的意思。很多人根本就沒見過這個關鍵字,不知道它的存在。也有很多程序員知道它的存在,但從來沒用過它。我對它有種“楊家有女初長成,養在深閨人未識” 的感覺。
volatile 關鍵字和const 一樣是一種類型修飾符,用它修飾的變量表示可以被某些編譯器未知的因素更改,比如操作系統、硬件或者其它線程等。遇到這個關鍵字聲明的變量,編譯器對訪問該變量的代碼就不再進行優化,從而可以提供對特殊地址的穩定訪問。
先看看下面的例子:
int i=10;
int j = i;//(1)語句
int k = i;//(2)語句
這時候編譯器對代碼進行優化,因為在(1)、(2)兩條語句中,i 沒有被用作左值。這時候編譯器認為i 的值沒有發生改變,所以在(1)語句時從內存中取出i 的值賦給j 之後,這個值並沒有被丟掉,而是在(2)語句時繼續用這個值給k 賦值。編譯器不會生成出匯編代碼重新從內存裡取i 的值,這樣提高了效率。但要注意:(1)、(2)語句之間i 沒有被用作左值才行。
再看另一個例子:
volatile int i=10;
int j = i;//(3)語句
int k = i;//(4)語句
volatile 關鍵字告訴編譯器i 是隨時可能發生變化的,每次使用它的時候必須從內存中取出i的值,因而編譯器生成的匯編代碼會重新從i 的地址處讀取數據放在k 中。這樣看來,如果i 是一個寄存器變量或者表示一個端口數據或者是多個線程的共享數據,就容易出錯,所以說volatile 可以保證對特殊地址的穩定訪問。但是注意:在VC++6.0 中,一般Debug 模式沒有進行代碼優化,所以這個關鍵字的作用有可能看不出來。你可以同時生成Debug 版和Release 版的程序做個測試。
明白volatile的初衷,最終決定什麼時候真正需要使用volatile
1.volatile 告訴編譯器i是隨時可能發生變化的,每次使用它的時候必須從i的地址中讀取,因而編譯器生成的可執行碼會重新從i的地址讀取數據放在k中。
2.而優化做法是,由於編譯器發現兩次從i讀數據的代碼之間的代碼沒有對i進行過操作,它會自動把上次讀的數據放在k中。而不是重新從i裡面讀。這樣以來,如果i是一個寄存器變量或者表示一個端口數據就容易出錯,所以說volatile可以保證對特殊地址的穩定訪問,不會出錯。
這裡還是花點時間說說編譯器優化吧:
由於內存訪問速度遠不及CPU處理速度,為提高機器整體性能,在硬件上引入硬件高速緩存Cache,加速對內存的訪問。另外在現代CPU中指令的執行並不一定嚴格按照順序執行,沒有相關性的指令可以亂序執行,以充分利用CPU的指令流水線,提高執行速度。以上是硬件級別的優化。再看軟件一級的優化:一種是在編寫代碼時由程序員優化,另一種是由編譯器進行優化。編譯器優化常用的方法有:將內存變量緩存到寄存器;調整指令順序充分利用CPU指令流水線,常見的是重新排序讀寫指令。對常規內存進行優化的時候,這些優化是透明的,而且效率很好。
volatile總是與優化有關,編譯器有一種技術叫做數據流分析,分析程序中的變量在哪裡賦值、在哪裡使用、在哪裡失效,分析結果可以用於常量合並,常量傳播等優化,進一步可以消除一些代碼。但有時這些優化不是程序所需要的,這時可以用volatile關鍵字禁止做這些優化。volatile的本意是“易變的” 因為訪問寄存器要比訪問內存單元快的多,所以編譯器一般都會作減少存取內存的優化,但有可能會讀髒數據。當要求使用volatile聲明變量值的時候,系統總是重新從它所在的內存讀取數據,即使它前面的指令剛剛從該處讀取過數據。精確地說就是,遇到這個關鍵字聲明的變量,編譯器對訪問該變量的代碼就不再進行優化,從而可以提供對特殊地址的穩定訪問;如果不使用valatile,則編譯器將對所聲明的語句進行優化。(簡潔的說就是:volatile關鍵詞影響編譯器編譯的結果,用volatile聲明的變量表示該變量隨時可能發生變化,與該變量有關的運算,不要進行編譯優化,以免出錯)
也許下面這個例子對你更有說服力
[html] view plaincopyprint?
#include <stdio.h>
void main()
{
int i=10;
int a = i;
printf("i= %d\n",a);
//下面匯編語句的作用就是改變內存中i的值,但是又不讓編譯器知道
__asm {
mov dword ptr [ebp-4], 20h
}
int b = i;
printf("i= %d\n",b);
}
#include <stdio.h>
void main()
{
int i=10;
int a = i;
printf("i= %d\n",a);
//下面匯編語句的作用就是改變內存中i的值,但是又不讓編譯器知道
__asm {
mov dword ptr [ebp-4], 20h
}
int b = i;
printf("i= %d\n",b);
} 運行於debug模式下,結果如下
[html] view plaincopyprint?
i = 10
i = 32
i = 10
i = 32運行於release模式下,結果則不然
[html] view plaincopyprint?
i = 10
i = 10
i = 10
i = 10輸出的結果明顯表明,release模式下,編譯器對代碼進行了優化,第二次沒有輸出正確的i值。
下面,我們把 i的聲明加上volatile關鍵字,看看有什麼變化:
[html] view plaincopyprint?
#include <stdio.h>
void main()
{
volatile int i=10;
int a = i;
printf("i= %d\n",a);
__asm {
mov dword ptr [ebp-4], 20h
}
int b = i;
printf("i= %d\n",b);
}
#include <stdio.h>
void main()
{
volatile int i=10;
int a = i;
printf("i= %d\n",a);
__asm {
mov dword ptr [ebp-4], 20h
}
int b = i;
printf("i= %d\n",b);
}分別在調試版本和release版本運行程序,輸出都是:
[html] view plaincopyprint?
i = 10
i = 32
i = 10
i = 32這樣一來你總該已經知道什麼時候該來麻煩volatile幫你禁止編譯器一廂情願的幫你進行優化了吧O(∩_∩)O~
待續。。。。