歡迎來到Linux教程網
Linux教程網
Linux教程網
Linux教程網
Linux教程網 >> Linux編程 >> Linux編程 >> C語言中volatile關鍵字的作用

C語言中volatile關鍵字的作用

日期:2017/3/1 10:25:14   编辑:Linux編程

一.前言

1.編譯器優化介紹:

由於內存訪問速度遠不及CPU處理速度,為提高機器整體性能,在硬件上引入硬件高速緩存Cache,加速對內存的訪問。另外在現代CPU中指令的執行並不一定嚴格按照順序執行,沒有相關性的指令可以亂序執行,以充分利用CPU的指令流水線,提高執行速度。以上是硬件級別的優化。再看軟件一級的優化:一種是在編寫代碼時由程序員優化,另一種是由編譯器進行優化。編譯器優化常用的方法有:將內存變量緩存到寄存器;調整指令順序充分利用CPU指令流水線,常見的是重新排序讀寫指令。對常規內存進行優化的時候,這些優化是透明的,而且效率很好。由編譯器優化或者硬件重新排序引起的問題的解決辦法是在從硬件(或者其他處理器)的角度看必須以特定順序執行的操作之間設置內存屏障(memory barrier),linux 提供了一個宏解決編譯器的執行順序問題。

void Barrier(void)

這個函數通知編譯器插入一個內存屏障,但對硬件無效,編譯後的代碼會把當前CPU寄存器中的所有修改過的數值存入內存,需要這些數據的時候再重新從內存中讀出。

2.volatile總是與優化有關,編譯器有一種技術叫做數據流分析,分析程序中的變量在哪裡賦值、在哪裡使用、在哪裡失效,分析結果可以用於常量合並,常量傳播等優化,進一步可以消除一些代碼。但有時這些優化不是程序所需要的,這時可以用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中則只能依靠硬件的良好設計。

Copyright © Linux教程網 All Rights Reserved