網(wǎng)站建設(shè)推廣特色新人學(xué)會seo
volatile限定符告知計算機,其他agent(而不是變量所在的程序)可以改變該變量的值。通常它被用于硬件地址以及在其他程序或同時運行的線程中共享數(shù)據(jù)。要求編譯器不要對其描述的對象作優(yōu)化處理,對它的讀寫都需要從內(nèi)存中訪問。
使用volatile關(guān)鍵字聲明變量,可以讓編譯器不對該變量進行優(yōu)化操作。如果在一些需要使用volatile關(guān)鍵字的地方而沒有使用它時,那么編譯器可能會優(yōu)化對該變量的訪問,并生成意外的代碼或達不到預(yù)期的功能。
目錄
1 volatile意味著什么
2 什么時候該使用volatile
3 不使用volatile可能出現(xiàn)的問題
4 強制使用特定指令來訪問內(nèi)存
?5 使用volatile和不使用volatile的匯編對比
6 總結(jié)
編譯器會優(yōu)化什么
作用
特點
1 volatile意味著什么
將變量聲明為volatile,是在告訴編譯器,該變量的值可能隨時會被其他實體(比如操作系統(tǒng)或者硬件)改變。該聲明可以保證編譯器不會優(yōu)化對該變量的使用,即使該變量未被使用或者未被更改。
2 什么時候該使用volatile
?對于可能在定義它們的作用域之外被修改的變量,使用volatile關(guān)鍵字。比如:
- 會被多個子程序讀寫的全局變量。如果程序在某些計算中使用到了全局變量,由于需要經(jīng)常對該變量進行讀寫操作,編譯器為了加速讀寫操作,會生成代碼將該變量的值加載到寄存器中,以執(zhí)行該計算。如果相同的全局變量隨后在另一個子程序中被使用,編譯器可能會直接讀取寄存器中的現(xiàn)有值,而不是去內(nèi)存當(dāng)中加載(寄存器中的是最新的值,還沒有更新到內(nèi)存中,而該子程序的本意是使用內(nèi)存中的值),這可能導(dǎo)致計算錯誤。這種直接讀取寄存器里的值的做法,是因為優(yōu)化器認(rèn)為該變量是non-volatile的,不能從外部被修改,而這種假設(shè)對于內(nèi)存映射的外設(shè)是不正確的。?
- 被用作定時(sleep或者timer)函數(shù)的延時變量。如果這種變量從未被使用過,編譯器可能會將整個延時函數(shù)代碼移除,除非將該變量聲明為volatile。
- 被中斷服務(wù)子程序調(diào)用的變量。如下示例代碼中,中斷子程序async_interrupt在類中聲明定義,但是它可能被硬件異步調(diào)用。被調(diào)用后會改變一個名為buffer_full的變量,如果不把buffer_full聲明為volatile,check_stream函數(shù)可能會讀不到buffer_full被async_interrupt更改后的值,導(dǎo)致程序錯誤。
-
class myclass {public:int check_stream();void async_interrupt();private:bool buffer_full; // must be declared as volatile }; int myclass::check_stream() {int count = 0;while (!buffer_full){count++;}return count; } void myclass::async_interrupt() {buffer_full = !buffer_full; }
-
此外:
- ?當(dāng)訪問一些內(nèi)存映射的外設(shè)時,必須使用volatile關(guān)鍵字聲明。就算編譯時采用 -O0(默認(rèn)不優(yōu)化),仍然不能保證每個變量都被當(dāng)作volatile。
volatile
?并不意味著內(nèi)部線程通信或者同步(inter-thread communication or synchronization),要達到這個目的,需要使用原子性操作,而不是volatile。- 中斷或者信號處理器必須使用原子性或者volatile sig_atomic_t?類型的變量,但不是任意類型的volatile變量,以確保多線程執(zhí)行下的同步。
- 在內(nèi)聯(lián)匯編代碼前,也可以使用volatile修飾。
3 不使用volatile可能出現(xiàn)的問題
如果需要使用volatile的時候而沒有使用,編譯器就會認(rèn)為該變量不會被當(dāng)前作用域外的其他實體修改。因此,編譯器可能會進行一些用戶意想不到的優(yōu)化操作,可能會導(dǎo)致:
- 在輪詢硬件時,代碼可能會陷入循環(huán)。
- 優(yōu)化可能會導(dǎo)致刪除實現(xiàn)故意定時延遲的代碼。
4 強制使用特定指令來訪問內(nèi)存
使用volatile關(guān)鍵字聲明變量,并不能保證使用特定的機器指令來訪問它。比如Cortex?-R7 and Cortex-R8的AXI外設(shè)端口是一個64-bit的外設(shè)寄存器。這個寄存器必須使用STM(多寄存寫入)來寫入,而不是STRD或者一對STR指令。不能保證編譯器在響應(yīng)相關(guān)變量或指針類型上的volatile修飾時選擇該寄存器所需的訪問方法。因此還可以使用volatile關(guān)鍵字來修飾內(nèi)聯(lián)匯編代碼。
__asm__ volatile("stm %1,{%Q0,%R0}" : : "r"(val), "r"(ptr));
__asm__ volatile("ldm %1,{%Q0,%R0}" : "=r"(val) : "r"(ptr));
?5 使用volatile和不使用volatile的匯編對比
有如下兩段代碼:
test1:
int buffer_full;
int read_stream(void)
{int count = 0;while (!buffer_full){count++;}return count;
}
test2:?
volatile int buffer_full;
int read_stream(void)
{int count = 0;while (!buffer_full){count++;}return count;
}
?test1和test2唯一的區(qū)別就是buffer_full變量是否被聲明為volatile。buffer_full是一個結(jié)束while循環(huán)的flag,當(dāng)buffer_full==true時,停止計數(shù),跳出循環(huán)并返回count。我們先看編譯結(jié)果,再進行分析:
使用?armclang --target=arm-arm-none-eabi -march=armv8-a -Os -S 命令對這兩段程序進行編譯:
test1:
1 read_stream:
2 movw r0, :lower16:buffer_full
3 movt r0, :upper16:buffer_full
4 ldr r1, [r0]
5 mvn r0, #0
6 .LBB0_1:
7 add r0, r0, #1
8 cmp r1, #0
9 beq .LBB0_1 ; infinite loop
10 bx lr
test2:
1 read_stream:
2 movw r1, :lower16:buffer_full
3 mvn r0, #0
4 movt r1, :upper16:buffer_full
5 .LBB1_1:
6 ldr r2, [r1] ; buffer_full
7 add r0, r0, #1
8 cmp r2, #0
9 beq .LBB1_1
10 bx lr
?先看test1的匯編程序,第6行到第9行為while循環(huán)語句,寄存器r1里的值為buffer_full,進入.LBB0_1的循環(huán)后,通過第8行的cmp指令不斷比較r1是否為0,以此判斷是否要跳出循環(huán)。由于test1沒有使用volatile關(guān)鍵字聲明變量buffer_full,所以在.LBB0_1的循環(huán)內(nèi)一直讀取的是寄存器r1的值,如果在輪詢的過程中,有其他子程序改變了buffer_full的值,test1也不會跳出循環(huán),因為它并不知道buffer_full已經(jīng)被更新了。
而在test2程序中,寄存器r1中存儲的是buffer_full的地址,r2中存儲的是它的值。由于使用了volatile聲明,編譯器認(rèn)為該變量是易變的,有被其他子程序更改的風(fēng)險,所有在第5行到第9行的循環(huán)中,每次讀取buffer_full的值都是使用?ldr r2, [r1]?,直接加載內(nèi)存當(dāng)中的值,而不是如test1中的,讀取寄存器的值。所以當(dāng)外部程序更改了buffer_full的值,test2就能立馬讀取到真實的buffer_full,從而跳出循環(huán)。
6 總結(jié)
智能的(進行優(yōu)化的)編譯器可能會把變量的值臨時儲存在寄存器上,便于下次讀取,以節(jié)約時間,這個過程被稱為高速緩存。但是有一些agent在內(nèi)存上改變了變量的值,寄存器上的還是舊數(shù)據(jù),這樣就出錯了。如果被volatile 關(guān)鍵字修飾,編譯器不會進行高速緩存,直接去內(nèi)存中讀取該變量的數(shù)據(jù)。
編譯器會優(yōu)化什么
- 將內(nèi)存變量緩存到寄存器中。
- 調(diào)整指令順序,充分利用CPU指令流水線,進行指令重新排序讀寫指令。
作用
告訴編譯器該變量值是不穩(wěn)定的,可能被更改,需要去內(nèi)存中讀取該值而不是讀取寄存器中的備份
- 多個線程都要用到的某個變量,而且變量的值會被改變
- 中斷服務(wù)子程序中訪問到的非自動變量
- 并行設(shè)備硬件寄存器的變量(如狀態(tài)寄存器)
特點
- 易變的
- 不可優(yōu)化,告訴編譯器不要對volatile聲明的變量進行各種優(yōu)化保證程序員寫在代碼中的指令一定會被執(zhí)行
- volatile int a;a = 1; 如果未聲明為volatile兩條代碼會合并成一條。
- 順序執(zhí)行的(原子性):保證volatile變量間的順序性,不會被編譯器進行亂序優(yōu)化
- 能否和const一起用:可以,const是只讀,volatile是去內(nèi)存中讀取
- 指針可以是volatile
- 可以修飾函數(shù)參數(shù)
?
參考文章:
Effect of the volatile keyword on compiler optimizationhttps://developer.arm.com/documentation/100748/0620/Writing-Optimized-Code/Effect-of-the-volatile-keyword-on-compiler-optimization?lang=en#a48-effect-of-the-volatile-keyword-on-compiler-optimization__c-code-for-nonvolatile-and-volatile-buffer-loops