前言
(1)volatile 關鍵字作為嵌入式面試的常考點,很多人都不是很了解,或者說一知半解。
(2)可能有些人會說了,volatile 關鍵字不就是防止編譯器優化的嗎?有啥好詳細講解的?那么,我就反問一句,為什么要防止編譯器優化,編譯器優化什么?編譯器優化之后會產生什么問題?
(3)今天我就來詳細解答一下這些疑惑。
軟件延時所造成的bug
(1)在初學51單片機的時候,我們都是使用軟件延時,例如下面是STC89,12MHZ晶振的1ms的軟件延時。
(2)有些人說,這樣寫延時可以啊,沒有問題。但是,假如你在MSP430中這樣寫,一定會產生bug。你會發現,軟件延時沒效果。
(3)這個時候,有些人會告訴你,要讓CCS的編輯優化等級降低然后就可以了。
(4)這是為什么呢?如下代碼,編譯會發現,這就是讓兩個變量進行自減,于是編譯器自作主張,認為這是沒有意義的代碼,并且將其刪除。于是,你看匯編代碼會發現,這里沒有進行自減操作。
(5)但是,如果你加上volatile 關鍵字,就會發現,軟件延時能夠正常運行。這個是為什么呢?volatile 關鍵字會告訴編譯器,這個變量你沒有權限動,你不要擅自主張的進行優化。
(6)因此,我們可以知道,volatile 關鍵字其實就是告訴編譯器,不要對變量進行優化。
void Delay1ms() //@12.000MHz
{unsigned char i, j;//加上volatile 關鍵字//volatile unsigned char i, j;i = 2;j = 239;do{while (--j);} while (--i);
}
外設寄存器被異步修改所產生的bug
(1)假設我們現在有一個外設寄存器叫做ExternalDevice ,這個寄存器會自動減少,地址為0x1000。(例如定時器的計數器就會自己增加或減少)
(2)現在我們要等ExternalDevice 寄存器值變成0的時候,再進行一些操作。
(3)但是你實際跑的時候會發現,這個地方要么無法阻塞,要么永遠阻塞。這是為什么呢?編譯器是不知道ExternalDevice這個變量是一個寄存器的,也不知道他最終是怎么變化的。所以他就會認為,這個地方是一個不變的變量進行反復判斷。他就會把while()這一行代碼刪除,認為是沒有意義的。
(4)于是我們需要加上volatile 告訴編譯器,這個東西你別動。
int main()
{//下面這三種寫法是等價的//int volatile *ExternalDevice = (uint8_t volatile *)0x1000; //volatile int *ExternalDevice = (uint8_t volatile *)0x1000; //volatile int *ExternalDevice = (volatile uint8_t *)0x1000; int *ExternalDevice = (uint8_t *)0x1000; // 假設外設寄存器的地址是 0x1000while(*ExternalDevice == 0); //等待這個外設寄存器的值變成0再進行操作
}
全局變量在中斷和正常運行的程序存在競爭問題
(1)比如群友給出一個這樣的代碼,發現一直無法實現點燈。感到非常的疑惑。
(2)這個地方就涉及到全局變量在中斷和正常運行的程序存在競爭問題。我們會發現,中斷程序和主函數里面都調用了全局變量a。但是,我們要知道,編譯器是無法知道運行態的情況的,他只能夠進行靜態優化。
(3)比如這里,編譯器他會認為,變量a的賦值是0。然后主函數里面的while()判斷是判斷他是否為0。這個時候,他無法查看到串口中斷的情況,就會認為,你就是要進行一個死循環。所以,最終這個程序最終會卡死在while(a == 0);這里。
(4)因此我們要將a加上volatile 關鍵字。
多線程共享變量
(1)當我們上了操作系統之后,都是會跑多線程的。
(2)但是跑多線程,就會存在一個問題,我們很可能會讓一個變量讓多個線程之間共享。例如,下面我們需要創建兩個線程,一個是GUI圖像顯示,一個是按鍵掃描。他們都需要共享要給變量key_num。這個時候,編譯器無法知道key_num什么時候會進行改變,所以他可能就會想,既然我不知道,我就不要他。所以,我們需要加上volatile 關鍵字,告訴編譯器,這里不要搞騷操作
uint8_t key_num;
//線程1
void GUI()
{while(1){switch(key_num){case key_short_down://...break;case key_long_down://...break;case key_up://...break;}}
}
//線程2
void key_scanf()
{while(1){if(key_Pin == HIGH) key_num = key_up;else if(key_time < 2000) key_num = key_short_down;else key_num = key_short_down;}
}
void main()
{register_task(GUI);register_task(key_scanf);while(1);
}
總結
(1)volatile 關鍵字本質就是編譯器防止優化。但是我們也要明白,為什么編譯器會進行優化。知道這個以后,我們才能夠更好的使用volatile 關鍵字。