volatile的中文意思—-易變的。在C語言中,volatile是一個類型修飾符(type specifier),volatile修飾的變量是說這個變量可能會被意想不到地改變,這樣編譯器就不會去假設這個變量的值了。精確地說就是,編譯器的優化器在 用到這個變量時必須每次都當心地從新從內存中讀取這個變量的值,而不是使用保存在寄存器裏的備份。html
http://zh.wikipedia.org/wiki/Volatile%E5%8F%98%E9%87%8Flinux
volatile關鍵字用來阻止(僞)編譯器對那些它認爲變量的值不能「被代碼自己」改變的 代碼上執行任何優化。程序員
在C,以及C++中,volatile關鍵字的做用[1]:緩存
容許訪問內存映射設備函數
容許在setjmp
和longjmp
之間使用變量oop
容許在信號處理函數中使用sig_atomic_t變量性能
volatile 關鍵字是一種類型修飾符,用它聲明的類型變量表示能夠被某些編譯器未知的因素更改,好比:操做系統、硬件或者其它線程等。遇到這個關鍵字聲明的變量,編譯器對訪問該變量的代碼就再也不進行優化,從而能夠提供對特殊地址的穩定訪問。聲明時語法:int volatile vInt; 當要求使用 volatile 聲明的變量的值的時候,系統老是從新從它所在的內存讀取數據,即便它前面的指令剛剛從該處讀取過數據。並且讀取的數據馬上被保存。例如:優化
1.編譯器優化介紹:atom
由 於內存訪問速度遠不及CPU處理速度,爲提升機器總體性能,在硬件上引入硬件高速緩存Cache,加速對內存的訪問。另外在現代CPU中指令的執行並不一 定嚴格按照順序執行,沒有相關性的指令能夠亂序執行,以充分利用CPU的指令流水線,提升執行速度。以上是硬件級別的優化。再看軟件一級的優化:一種是在 編寫代碼時由程序員優化,另外一種是由編譯器進行優化。編譯器優化經常使用的方法有:將內存變量緩存到寄存器;調整指令順序充分利用CPU指 令流水線,常見的是從新排序讀寫指令。對常規內存進行優化的時候,這些優化是透明的,並且效率很好。由編譯器優化或者硬件從新排序引發的問題的解決辦法是 在從硬件(或者其餘處理器)的角度看必須以特定順序執行的操做之間設置內存屏障(memory barrier),linux 提供了一個宏解決編譯器的執行順序問題。spa
void Barrier(void)
這個函數通知編譯器插入一個內存屏障,但對硬件無效,編譯後的代碼會把當前CPU寄存器中的全部修改過的數值存入內存,須要這些數據的時候再從新從內存中讀出。
2.volatile 老是與優化有關,編譯器有一種技術叫作數據流分析,分析程序中的變量在哪裏賦值、在哪裏使用、在哪裏失效,分析結果能夠用於常量合併,常量傳播等優化,進 一步能夠消除一些代碼。但有時這些優化不是程序所須要的,這時能夠用volatile關鍵字禁止作這些優化。
1.volatile的本意是「易變的」 由於訪問寄存器要比訪問內存單元快的多,因此編譯器通常都會做減小存取內存的優化,但有可能會讀髒數據。當要求使用volatile聲明變量值的時候,系統老是從新從它所在的內存讀取數據, 即便它前面的指令剛剛從該處讀取過數據。精確地說就是,遇到這個關鍵字聲明的變量,編譯器對訪問該變量的代碼就再也不進行優化,從而能夠提供對特殊地址的穩 定訪問;若是不使用valatile,則編譯器將對所聲明的語句進行優化。(簡潔的說就是:volatile關鍵詞影響編譯器編譯的結果,用 volatile聲明的變量表示該變量隨時可能發生變化,與該變量有關的運算,不要進行編譯優化,以避免出錯)
2.看兩個事例:
1>告訴compiler不能作任何優化
好比要往某一地址送兩指令:
int *ip =...; //設備地址
*ip = 1; //第一個指令
*ip = 2; //第二個指令
以上程序compiler可能作優化而成:
int *ip = ...;
*ip = 2;
結果第一個指令丟失。若是用volatile, compiler就不容許作任何的優化,從而保證程序的原意:
volatile int *ip = ...;
*ip = 1;
*ip = 2;
即便你要compiler作優化,它也不會把兩次付值語句間化爲一。它只能作其它的優化。
2>用volatile定義的變量會在程序外被改變,每次都必須從內存中讀取,而不能重複使用放在cache或寄存器中的備份。
例如:
volatile char a;
a=0;
while(!a){
//do some things;
}
doother();
若是沒有 volatiledoother()不會被執行
3.下面是使用volatile變量的幾個場景:
1>中斷服務程序中修改的供其它程序檢測的變量須要加volatile;
例如:
static int i=0;
int main(void)
{
...
while (1){
if (i) dosomething();
}
}
/* Interrupt service routine. */
void ISR_2(void)
{
i=1;
}
程 序的本意是但願ISR_2中斷產生時,在main函數中調用dosomething函數,可是,因爲編譯器判斷在main函數裏面沒有修改過i,所以可能 只執行一次對從i到某寄存器的讀操做,而後每次if判斷都只使用這個寄存器裏面的「i副本」,致使dosomething永遠也不會被調用。若是將變量加 上volatile修飾,則編譯器保證對此變量的讀寫操做都不會被優化(確定執行)。此例中i也應該如此說明。
2>多任務環境下各任務間共享的標誌應該加volatile
3>存儲器映射的硬件寄存器一般也要加voliate,由於每次對它的讀寫均可能有不一樣意義。
例如:
假設要對一個設備進行初始化,此設備的某一個寄存器爲0xff800000。
int *output = (unsigned int *)0xff800000;//定義一個IO端口;
int init(void)
{
int i;
for(i=0;i< 10;i++){
*output = i;
}
}
通過編譯器優化後,編譯器認爲前面循環半天都是廢話,對最後的結果毫無影響,由於最終只是將output這個指針賦值爲9,因此編譯器最後給你編譯編譯的代碼結果至關於:
int init(void)
{
*output = 9;
}
如 果你對此外部設備進行初始化的過程是必須是像上面代碼同樣順序的對其賦值,顯然優化過程並不能達到目的。反之若是你不是對此端口反覆寫操做,而是反覆讀操 做,其結果是同樣的,編譯器在優化後,也許你的代碼對此地址的讀操做只作了一次。然而從代碼角度看是沒有任何問題的。這時候就該使用volatile通知 編譯器這個變量是一個不穩定的,在遇到此變量時候不要優化。
例如:
volatile int *output=(volatile unsigned int *)0xff800000;//定義一個I/O端口
另外,以上這幾種狀況常常還要同時考慮數據的完整性(相互關聯的幾個標誌讀了一半被打斷了重寫),在1中能夠經過關中斷來實現,2中禁止任務調度,3中則只能依靠硬件的良好設計。
4.幾個問題
1)一個參數既能夠是const還能夠是volatile嗎?
能夠的,例如只讀的狀態寄存器。它是volatile由於它可能被意想不到地改變。它是const由於程序不該該試圖去修改它。
2) 一個指針能夠是volatile 嗎?
能夠,當一箇中服務子程序修該一個指向一個buffer的指針時。
5.volatile的本質:
1> 編譯器的優化
在本次線程內, 當讀取一個變量時,爲提升存取速度,編譯器優化時有時會先把變量讀取到一個寄存器中;之後,再取變量值時,就直接從寄存器中取值;當變量值在本線程裏改變時,會同時把變量的新值copy到該寄存器中,以便保持一致。
當變量在因別的線程等而改變了值,該寄存器的值不會相應改變,從而形成應用程序讀取的值和實際的變量值不一致。
當該寄存器在因別的線程等而改變了值,原變量的值不會改變,從而形成應用程序讀取的值和實際的變量值不一致。
2>volatile應該解釋爲「直接存取原始內存地址」比較合適,「易變的」這種解釋簡直有點誤導人。
C++關鍵字:mutable、volatile、explicit以及__based
mutable關鍵字
關鍵字mutable是C++中一個不經常使用的關鍵字,他只能用於類的非靜態和很是量數據成員咱們知道一個對象的狀態由該對象的非靜態數據成員決定,因此隨着數據成員的改變,對像的狀態也會隨之發生變化!
若是一個類的成員函數被聲明爲const類型,表示該函數不會改變對象的狀態,也就是該函數不會修改類的非靜態數據成員.可是有些時候須要在該類函數中對類的數據成員進行賦值.這個時候就須要用到mutable關鍵字了
class CDemo {
public:
CDemo(){}
virtual ~CDemo(){}
public:
bool getFlag() const
{
m_nAccess ++;
return m_bFlag;
}
private:
int m_nAccess;
bool m_bFlag;
};
編譯上面的代碼會出現 error C2166: l-value specifies const object的錯誤說明在const類型的函數中改變了類的非靜態數據成員.這個時候須要使用mutable來修飾一下要在const成員函數中改變的非靜態數據成員
m_nAccess,代碼以下:
class CDemo {
public:
CDemo(){}
virtual ~CDemo(){}
public:
bool getFlag() const
{
m_nAccess ++;
return m_bFlag;
}
private:
mutable int m_nAccess;
bool m_bFlag;
};
volatile關鍵字 類型修飾符
volatile是c/c++中一個不爲人知的關鍵字,該關鍵字告訴編譯器不要持有變量在寄存器中的臨時拷貝,它能夠適用於基礎類型如:int,char,long......也適用於C的結構和C++的類。當對結構或者類對象使用volatile修飾的時候,結構或者類的全部 成員都會被視爲volatile.使用volatile並不會否認對CRITICAL_SECTION,Mutex,Event等同步對象的須要 .
例如:
int i; i= i+3; 不管如何,老是會有一小段時間,i會被放在一個寄存器中,由於算術運算只能在寄存器中進行。通常來講,volatitle關鍵字適用於行與行之間,而不是放在行內。 咱們先來實現一個簡單的函數,來觀察一下由編譯器產生出來的彙編代碼中的不足之處,並觀察volatile關鍵字如何修正這個不足之處。在這個函數體內存在一個busy loop(所謂busy loop也叫作busy waits,是一種高度浪費CPU時間的循環方法) void getKey(char* pch) { while (*pch == 0); } explicit關鍵字 咱們在編寫應用程序的時候explicit關鍵字基本上是不多使用,它的做用是"禁止單參數構造函數"被用於自動型別轉換,其中比較典型的例子就是容器類型,在這種類型的構造函數中你能夠將初始長度做爲參數傳遞給構造函數. 例如: 你能夠聲明這樣一個構造函數 class Array { public: explicit Array(int size); }; 在這裏explicit關鍵字起着相當重要的做用,若是沒有這個關鍵字的話,這個構造函數有能力將int轉換成Array.一旦這種狀況發生,你能夠給Array支派一個整數值而不會引發任何的問題,好比: Array arr; arr = 40; 此時,C++的自動型別轉換會把40轉換成擁有40個元素的Array,而且指派給arr變量,這個結果根本就不是咱們想要的結果.若是咱們將構造函數聲明爲explicit,上面的賦值操做就會致使編譯器報錯,使咱們能夠及時發現錯誤.須要注意的是:explicit一樣也能阻止"以賦值語法進行帶有轉型操做的初始化"; Array arr(40);//正確 Array arr = 40;//錯誤 __based關鍵字 該關鍵字主要用來解決一些和共享內存有關的問題,它容許指針被定義爲從某一點開始算的32位偏移值,而不是內存中的絕對位置 舉個例子: