作嵌入式C開發的相信都使用過一個關鍵字volatile,特別是作底層開發的。假設一個GPIO的數據寄存器地址是0x50000004,咱們通常會定義一個這樣的宏:html
#define GDATA *((volatile unsigned int*)0x50000004)
在面試的時候也會被問到過volatile關鍵字起什麼做用?linux
網絡上的回答通常是防止被編譯器優化,或者還會加一點就是訪問被volatile修飾的變量時,強制訪問內存中的值,而不是緩存中的。面試
我對上面的回答一直存在誤解,覺得是:由於被編譯器優化,因此致使存取變量時是存取變量在cache中的緩存。shell
點我vim
volatile緩存
Indicates that a variable can be changed by a background routine.網絡
Keyword volatile is an extreme opposite of const. It indicates that a variable may be changed in a way which is absolutely unpredictable by analysing the normal program flow (for example, a variable which may be changed by an interrupt handler). This keyword uses the following syntax:優化
volatile data-definition;
Every reference to the variable will reload the contents from memory rather than take advantage of situations where a copy can be in a register.調試
volatile
代表變量能被後臺程序修改code
關鍵字volatile和const是徹底相反的。它代表變量可能會經過某種方式發生改變,而這種方式是你經過分析正常的程序流程徹底預測不出來的。(例如,一個變量可能被中斷處理程序修改)。關鍵字使用語法以下:
volatile data-definition;
每次對變量內容的引用會從新從內存中加載而不是從變量在寄存器裏面的拷貝加載。
個人理解:以中斷處理程序修改變量解釋可能不太合適,以GPIO爲例最合適。首先什麼是變量?變量就是一塊編了地址的內存區域。GPIO的數據寄存器有一個地址,大小通常爲32bit,因此這個數據寄存器能夠認爲就是一個變量,咱們能夠讀寫它。若是GPIO設置爲輸入,修改GPIO數據寄存器這個變量的就是這個GPIO的引腳,無論你如何分析你的程序,你不可能知道這個GPIO數據寄存器裏面值是多少,你得讀它。你此刻讀到數據和下一刻讀到的徹底多是不同的。簡單的說就是你要的數據不一樣步。使用volatile修飾後,會強制你每次引用GPIO寄存器對應的變量時都會去它的寄存器裏面讀。
爲了搞清楚volatile究竟是怎麼影響編譯器行爲的,須要以具體的例子來講明:
下面是a.c文件:
int main(void) { volatile char a; a = 5; a = 7; return 0; }
下面是b.c文件:
int main(void) { char a; a = 5; a = 7; return 0; }
進行編譯,分別獲得它們對應的彙編文件:
$ make arm-linux-gcc -S a.c -o a.s arm-linux-gcc -S b.c -o b.s
對比a.s、b.s:
vimdiff a.s b.s
能夠看到無任何差別,爲何呢?由於咱們gcc的優化等級是默認等級,如今把優化等級調至-O3。再看對比
$ make arm-linux-gcc -O3 -S a.c -o a.s arm-linux-gcc -O3 -S b.c -o b.s
能夠看到未加volatile修飾的文件b.c,在優化後,彙編對應的a=5;a=7;
這兩個語句直接優化沒了。a=1;a=0;
假設a是控制GPIO的語句,原來打算是讓GPIO先拉高,再拉低,實現某種時序,結果優化一開,這兩句直接廢了。這讓你在調試硬件的時候會感到莫名其妙。因此這種狀況得像a.c那樣用volatile來修飾。
這裏是防止編譯器優化掉相關語句,而不是優化變量的存取對象(memory or register)。
a.c
int main(void) { int b; int c; volatile int* a = (int*)0x30000000; b = *a; c = *a; return c + b; }
b.c
int main(void) { int b; int c; int* a = (int*)0x30000000; b = *a; c = *a; return c + b; }
a.s
mov r3, #805306368 ldr r2, [r3] @ b = *a; ldr r0, [r3] @ c = *a; add r0, r0, r2 @ b + c; bx lr
b.s
mov r3, #805306368 ldr r0, [r3] @ b = *a; mov r0, r0, asl #1 @ b << 2; 也就是 b * 2;也就是 b + b;也就是 add r0, r0, r0(可能這句彙編不合法) bx lr
能夠看到b.s被優化後,第一次取*a
的值時,是從地址0x30000000取出來的(ldr r0, [r3]
),第二次就直接沒取了,是直接使用了r0的值。這裏的r0就是*a
的緩存。我以前在這裏的理解存在很大的錯誤:
訪問被volatile修飾的變量時,強制訪問內存中的值,而不是緩存中的。
覺得這裏的緩存指cache。
畢竟從彙編指令優化着手怎麼可能控制變量的必定從cache裏面存取。
你不能假定一個共享(GPIO引腳和你的程序共享了GPIO數據寄存器)的變量只會被你修改。就比如你讀一個文件的內容,過10秒後,你不能假定文件內容沒變,必需要從新讀取文件內容。
volatile關鍵詞影響編譯器編譯的結果,用volatile聲明的變量表示該變量隨時可能發生變化,與該變量有關的運算,不要進行編譯優化,以避免出錯
一個例子:
int main(void) { int b; volatile int* a = (int*)0x30000000; b = (*a) * (*a); return b; }
彙編後
mov r3, #805306368 ldr r2, [r3] ① ldr r0, [r3] ② mul r0, r2, r0 bx lr
程序本意是要計算平方。若是這段代碼在運行至①這行彙編時,被調度開了,過了一陣調度回來繼續運行②行,此時徹底有可能 R2 != R0。那麼計算出來的結果R0必然不等於那個平方值。