C语言中volatilekeyword的作用
一.前言
1.編譯器優化介紹:
由于內存訪問速度遠不及CPU處理速度,為提高機器總體性能,在硬件上引入硬件快速緩存Cache,加速對內存的訪問。另外在現代CPU中指令的運行并不一定嚴格依照順序運行,沒有相關性的指令能夠亂序運行,以充分利用CPU的指令流水線,提高運行速度。以上是硬件級別的優化。再看軟件一級的優化:一種是在編寫代碼時由程序猿優化,還有一種是由編譯器進行優化。編譯器優化經常使用的方法有:將內存變量緩存到寄存器;調整指令順序充分利用CPU指令流水線,常見的是又一次排序讀寫指令。對常規內存進行優化的時候,這些優化是透明的,并且效率非常好。由編譯器優化或者硬件又一次排序引起的問題的解決的方法是在從硬件(或者其他處理器)的角度看必須以特定順序運行的操作之間設置內存屏障(memory barrier),linux 提供了一個宏解決編譯器的運行順序問題。
void Barrier(void)
這個函數通知編譯器插入一個內存屏障,但對硬件無效,編譯后的代碼會把當前CPU寄存器中的全部改動過的數值存入內存,須要這些數據的時候再又一次從內存中讀出。
2.volatile總是與優化有關,編譯器有一種技術叫做數據流分析,分析程序中的變量在哪里賦值、在哪里使用、在哪里失效,分析結果能夠用于常量合并,常量傳播等優化,進一步能夠消除一些代碼。但有時這些優化不是程序所須要的,這時能夠用volatilekeyword禁止做這些優化。
二.volatile具體解釋:
1.volatile的本意是“易變的” 由于訪問寄存器要比訪問內存單元快的多,所以編譯器一般都會作降低存取內存的優化,但有可能會讀臟數據。當要求使用volatile聲明變量值的時候,系統總是又一次從它所在的內存讀取數據,即使它前面的指令剛剛從該處讀取過數據。精確地說就是,遇到這個keyword聲明的變量,編譯器對訪問該變量的代碼就不再進行優化,從而能夠提供對特殊地址的穩定訪問;假設不使用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;//定義一個IOport;
int?? init(void)
{
????? int i;
?? ?? for(i=0;i< 10;i++){
??????? ?*output = i;
}
}
經過編譯器優化后,編譯器覺得前面循環半天都是廢話,對最后的結果毫無影響,由于終于僅僅是將output這個指針賦值為9,所以編譯器最后給你編譯編譯的代碼結果相當于:
int? init(void)
{
????? *output = 9;
}
假設你對此外部設備進行初始化的過程是必須是像上面代碼一樣順序的對其賦值,顯然優化過程并不能達到目的。反之假設你不是對此port重復寫操作,而是重復讀操作,其結果是一樣的,編譯器在優化后,或許你的代碼對此地址的讀操作僅僅做了一次。然而從代碼角度看是沒有不論什么問題的。這時候就該使用volatile通知編譯器這個變量是一個不穩定的,在遇到此變量時候不要優化。
比如:
volatile? int?*output=(volatile unsigned int *)0xff800000;//定義一個I/Oport
另外,以上這幾種情況經常還要同一時候考慮數據的完整性(相互關聯的幾個標志讀了一半被打斷了重寫),在1中能夠通過關中斷來實現,2中禁止任務調度,3中則僅僅能依靠硬件的良好設計。
4.幾個問題
?1)一個參數既能夠是const還能夠是volatile嗎?
能夠的,比如僅僅讀的狀態寄存器。它是volatile由于它可能被意想不到地改變。它是const由于程序不應該試圖去改動它。
2) 一個指針能夠是volatile 嗎?
能夠,當一個中服務子程序修該一個指向一個buffer的指針時。
5.volatile的本質:
1> 編譯器的優化
在本次線程內, 當讀取一個變量時,為提高存取速度,編譯器優化時有時會先把變量讀取到一個寄存器中;以后,再取變量值時,就直接從寄存器中取值;當變量值在本線程里改變時,會同一時候把變量的新值copy到該寄存器中,以便保持一致。
當變量在因別的線程等而改變了值,該寄存器的值不會對應改變,從而造成應用程序讀取的值和實際的變量值不一致。
當該寄存器在因別的線程等而改變了值,原變量的值不會改變,從而造成應用程序讀取的值和實際的變量值不一致。
2>volatile應該解釋為“直接存取原始內存地址”比較合適,“易變的”這樣的解釋簡直有點誤導人。
6.以下的函數有什么錯誤:
int square(volatile int *ptr)
{
return *ptr * *ptr;
}
該程序的目的是用來返指針*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;
}
注意:頻繁地使用volatile非常可能會添加�代碼尺寸和降低性能,因此要合理的使用volatile。
?
總結
以上是生活随笔為你收集整理的C语言中volatilekeyword的作用的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: vs2013 乱码问题
- 下一篇: 凤翅医话——小青龙加石膏汤