# volatile ###### tags: `C basics` ### Reference >致謝 >https://www.itread01.com/content/1550476641.html >https://blog.xuite.net/tzeng015/twblog/113273164 ### Basics 由於訪問暫存器的速度比訪問RAM快,所以編譯器一般都會作減少存取外部RAM的優化。當要求使用volatile宣告的變數的值的時候,系統會重新從它所在的記憶體讀取資料,即使它前面的指令剛剛從該處讀取過資料。而且讀取的資料立刻被儲存。 **精確地說就是優化器在用到這個變數時必須每次都小心地重新從記憶體裡讀取這個變數的值,而不是使用儲存在暫存器裡的備份。當我們不能確保暫存器裡的值一定不會變的情況下就需要使用volatile** 以下面為例 ```c= define rURXH0 (*(volatile unsigned char *)0x50000024) //UART 0 Receive buffer ``` 這個就是序列的接收buffer,其地址為0x50000024,如果我們沒有將這個地址強行轉換成volatile,那麼我們在使用rURXH0時,可能直接從CPU的暫存器中取值。因為之前rURXH0被訪問過,也就是說之前就從記憶體中取出rURXH0的值儲存到某個暫存器中。 之所以直接從暫存器中取值,而不去記憶體中取值,是因為編譯器優化程式碼的結果,如果用volatile關鍵字對0x50000024進行強制轉換,使得每一次被訪問rURXH0時,執行部件都會從0x50000024這個記憶體單元中取出值來賦值給rURXH0。 一般說來,volatile需要用在以下的幾個地方: 1、中斷服務程式中修改的供其它程式檢測的變數需要加volatile 2、多工環境下各任務間共享的標誌應該加volatile 3、儲存器對映的硬體暫存器通常也要加volatile,因為每次對它的讀寫可能有不同意義 以下程式碼等待記憶體變數gpio_flag的值變為1,之後執行do2() ```c= int gpio_flag; void test(){ do1(); while(gpio_flag==0); do2(); } ``` gpio_flag的值由程式其他地方更改,尤其可能是某個硬體中斷服務函式。 例如:某個按鈕按下=>產生中斷,在interrupt handler中修改gpio_flag為1,這樣上面的程式就能夠得以繼續執行。 但是,編譯器並不知道flag的值會被別的程式修改,因此在它進行優化的時候,可能會把flag的值先讀入某個暫存器,然後等待那個暫存器變為1。如果不幸進行了這樣的優化,那麼while迴圈就變成了死迴圈,因為暫存器的內容不可能被中斷服務程式修改。 為了讓程式每次都讀取真正flag變數的值,就需要定義為如下形式 ```c= volatile int gpio_flag; ``` 需要注意的是,沒有volatile也可能能正常執行,但是可能修改了編譯器的優化級別之後就又不能正常運行了。因此經常會出現debug版本正常,但是release版本卻不能正常的問題。 ### Questions 1. 一個參數可以同時是const也是volatile嗎? 解釋為什麼。 2. 一個指標可以是volatile嗎? 解釋為什麼。 3. 下面的函數有什麼錯誤︰ ```c= int square(volatile int *ptr){ return *ptr * *ptr; } ``` 1. 可以,例如"Read-Only Status Register"它是volatile因為它可能被意想不到地改變。它是const因為程式不應該試圖去修改它。 2. 可以,參見 https://stackoverflow.com/questions/9935190/why-is-a-point-to-volatile-pointer-like-volatile-int-p-useful 3. 這段程式碼有點變態。這段程式碼的目的是用來返回指標ptr指向值的平方,由於ptr指向一個volatile型參數,編譯器將產生類似下面的程式碼 ```c= int square(volatile int *ptr) { int a,b; a = *ptr; b = *ptr; return a * b; } ``` 由於ptr的值可能被意想不到地改變,因此a和b可能是不同的。結果,這段程式碼可能返回不是你所期望的平方值!正確的程式碼如下 ```c= int square(volatile int *ptr) { int a; a = *ptr; return a * a; } ```