volatile關鍵字詳解,以及在嵌入式中的應用
volatile 關鍵字的作用是作為指令關鍵字,確保本條指令不會因為編譯器的優化而省略,且要求每次直接讀值。
關鍵字 volatile 是與 const 絕對對立的。
二、在嵌入式中使用volatile
變數如果加了 volatile 修飾,則會從記憶體重新裝載內容,而不是直接從暫存器拷貝內容。
volatile應用比較多的場合,在中斷服務程式和cpu相關暫存器的定義。
volatile 關鍵字是一種型別修飾符,用它宣告的型別變數表示可以被某些編譯器未知的因素更改。volatile 提醒編譯器它後面所定義的變數隨時都有可能改變,因此編譯後的程式每次需要儲存或讀取這個變數的時候,都會直接從變數位址中讀取資料。如 果沒有 volatile 關鍵字,則編譯器可能優化讀取和儲存,可能暫時使用暫存器中的值,如果這個變數由別的程式更新了的話,將出現不一致的現象。所以遇到這個關鍵字宣告的變數,編譯器對訪問該變數的**就不再進行優化,從而可以提供對特殊位址的穩定訪問。
//編譯led.c檔案
#define gpc1con *((volatile unsigned int*)0xe0200080)
#define gpc1dat *((volatile unsigned int*)0xe0200084)
#define gpc1pud *((volatile unsigned int*)0xe0200088)
//隱式宣告
void delay (unsigned int);
void led_test (void) }
void delay (unsigned int n)
編譯:arm-linux-gcc -c led.c -o led.o –nostdlib
不使用標準庫,生成led.o檔案
三、volatile 使用
1、並行裝置的硬體暫存器(如:狀態暫存器)
儲存器對映的硬體暫存器通常也要加 voliate,因為每次對它的讀寫都可能有不同意義。
例如:假設要對乙個裝置進行初始化,此裝置的某乙個暫存器為0xff800000。
int *output = (unsigned int *)0xff800000;//定義乙個io埠;
int init(void)
}
經過編譯器優化後,編譯器認為前面迴圈半天都是廢話,對最後的結果毫無影響,因為最終只是將output這個指標賦值為 9,所以編譯器最後給你編譯編譯的**結果相當於:
int init(void)
如果你對此外部裝置進行初始化的過程是必須是像上面**一樣順序的對其賦值,顯然優化過程並不能達到目的。反之如果你不是對此埠反覆寫操作,而是反覆讀操作,其結果是一樣的,編譯器在優化後,也許你的**對此位址的讀操作只做了一次。然而從**角度看是沒有任何問題的。這時候就該使用volatile通知編譯器這個變數是乙個不穩定的,在遇到此變數時候不要優化。
//編譯led.c檔案
#define gpc1con *((volatile unsigned int*)0xe0200080)
#define gpc1dat *((volatile unsigned int*)0xe0200084)
#define gpc1pud *((volatile unsigned int*)0xe0200088)
//隱式宣告
void delay (unsigned int);
void led_test (void) }
void delay (unsigned int n)
編譯:arm-linux-gcc -c led.c -o led.o –nostdlib
不使用標準庫,生成led.o檔案
#define gpc1con *((volatile unsigned int*)0xe0200080) 怎麼理解?
這裡其實就是定義了乙個指標變數。
gpc1con 為暫存器名稱、0xe0200080 為暫存器位址、(volatile unsigned int*) 為強制型別轉換。
我們知道 volatile 和 const 一樣為型別修飾符,不改變變數型別。
暫存器位址為什麼要加 volatile 修飾呢?
是因為,這些暫存器裡面的值是隨時變化的。如果我們沒有將這個位址強制型別轉換成 volatile,那麼我們在使用gpc1con 這個暫存器的時候, 會直接從 cpu 的暫存器中取值。因為之前gpc1con 被訪問過,也就是之前就從記憶體中取出 gpc1con 的值儲存到某個暫存器中。之所以直接從暫存器中取值,而不去記憶體中取值,是因為編譯器優化**的結果(訪問 cpu暫存器比訪問 ram 快的多)。用 volatile 關鍵字對 0xe0200080 進行強制轉換,使得每一次訪問 gpc1con 時,執行部件都會從 0xe0200080 這個記憶體單元中取出值來賦值給 gpc1con 。
2、乙個中斷服務子程式中會訪問到的非自動變數(non-automatic variables)
由於訪問暫存器的速度要快過ram,所以編譯器一般都會作減少訪問外部ram的優化,例如:
static int i=0; //i 為非自動變數
int main(void)
}/* interrupt service routine. */
void isr_2(void)
程式的本意是希望 isr_2 中斷產生時,在main函式中呼叫 dosomething 函式,但是,由於編譯器判斷在 main 函式裡面沒有修改過 i,因此可能只執行一次對從i到某暫存器的讀操作,然後每次if判斷都只使用這個暫存器裡面的「i副本」,導致 dosomething 永遠也不會被呼叫。如果將變數加上 volatile 修飾,則編譯器保證對此變數的讀寫操作都不會被優化(肯定執行)。此例中i也應該如此說明。
3、多執行緒應用中被幾個任務共享的變數
當兩個執行緒都要用到某乙個變數且該變數的值會被改變時,應該用 volatile 宣告,該關鍵字的作用是防止優化編譯器把變數從記憶體裝入cpu暫存器中。如果變數被裝入暫存器,那麼兩個執行緒有可能乙個使用記憶體中的變數,乙個使用暫存器中的變數,這會造成程式的錯誤執行。volatile的意思是讓編譯器每次操作該變數時一定要從記憶體中真正取出,而不是使用已經存在暫存器中的值,如下:
volatile bool bstop = false; //bstop 為共享全域性變數
(1) 在乙個執行緒中:
while( !bstop )
bstop = false;
return;
(2) 在另外乙個執行緒中,要終止上面的執行緒迴圈:
bstop = true;
while( bstop );
等待上面的執行緒終止,如果bstop不使用volatile申明,那麼這個迴圈將是乙個死迴圈,因為bstop已經讀取到了暫存器中,暫存器中bstop的值永遠不會變成false,加上volatile,程式在執行時,每次均從記憶體中讀出bstop的值,就不會死迴圈了。
總結:volatile 關鍵字是一種型別修飾符,用它宣告的型別變數表示可以被某些編譯器未知的因素更改。volatile 提醒編譯器它後面所定義的變數隨時都有可能改變,因此編譯後的程式每次需要儲存或讀取這個變數的時候,都會直接從變數位址中讀取資料。如 果沒有 volatile 關鍵字,則編譯器可能優化讀取和儲存,可能暫時使用暫存器中的值,如果這個變數由別的程式更新了的話,將出現不一致的現象。所以遇到這個關鍵字宣告的變數,編譯器對訪問該變數的**就不再進行優化,從而可以提供對特殊位址的穩定訪問。
更詳細的解釋 可以參考
c語言中volatile關鍵字
volatile關鍵字是一種型別修飾符,用它宣告的型別變數表示可以被某些編譯器未知的因素更改 比如 作業系統 硬體或者其它執行緒等。遇到這個關鍵字宣告的變數,編譯器對訪問該變數的 就不再進行優化,從而可以提供對特殊位址的穩定訪問。使用該關鍵字的例子如下 int volatile nvint 當要求使...
C語言中volatile關鍵字
一 前言 程式的優化 1 硬體級別上的優化 a.由於記憶體訪問速度遠不及cpu處理速度,為提高機器整體效能,在硬體上引入硬體快取記憶體cache,加速對記憶體的訪問。b.在現代cpu中指令的執行並不一定嚴格按照順序執行,沒有相關性的指令可以亂序執行,以充分利用cpu的指令流水線,提高執行速度。2 軟...
C語言中volatile關鍵字
1.編譯器優化介紹 由於記憶體訪問速度遠不及cpu處理速度,為提高機器整體效能,在硬體上引入硬體快取記憶體cache,加速對記憶體的訪問。另外在現代cpu中指令的執行並不一定嚴格按照順序執行,沒有相關性的指令可以亂序執行,以充分利用cpu的指令流水線,提高執行速度。以上是硬體級別的優化。再看軟體一級...