人妻蜜と1~4中文字幕月野定规 ,国产精品成人va在线播放,色优久久久久综合网鬼色,WWW插插插无码视频网站

【ARM】MDK-volatile關(guān)鍵字對編譯器優(yōu)化的影響

1、 問題場景

在聲明那些不應(yīng)該被編譯器優(yōu)化的變量時(shí),我們一般會選擇使用 volatile 關(guān)鍵字,否則,編譯器可能會優(yōu)化對該變量的訪問,從而生成意外的代碼,或移除預(yù)期的功能。因此,對于 volatile 關(guān)鍵字的使用,以及它對編譯器優(yōu)化的影響,都值得進(jìn)行詳細(xì)的了解和學(xué)習(xí)。


2、軟硬件環(huán)境

1)、軟件版本:MDK5.40

2)、電腦環(huán)境:Windows 10

3)、外設(shè)硬件:無


3、解決方法

1)、沒有加volatile關(guān)鍵字時(shí),函數(shù)的反匯編表現(xiàn)如圖1所示:


圖 1

反匯編解讀:

0x0800020C F240110C MOVW r0, #0x10C

0x08000210 F2C20100 MOVT r0, #0x2000

這兩句指令將變量 buffer_full 的地址加載到寄存器 r0 中,此時(shí) r0= 0x200010C,后續(xù)方便直接從這個地址讀取數(shù)據(jù)。

0x08000214 6800 LDR r0,[r0,#0x00]

這里是從 r0 存儲的內(nèi)存地址里 (即 buffer_full 的地址)讀取值,并保存值到寄存器 r0。即讀取 buffer_full 的當(dāng)前值。


0x08000216 FAB0F080 CLZ r0,r0

這里是計(jì)算 r0 的前導(dǎo)零的數(shù)量,并把這個數(shù)量存儲在 r0 中。

(CLZ 指令會從最高有效位(最左側(cè)的位)開始計(jì)算連續(xù)的零。對于全零的值,所有 32 位都是零的,會輸出 32。舉例:如果執(zhí)行 CLZ 前 r0=0x0000 0000,那么執(zhí)行后 r0=32。如果執(zhí)行 CLZ 前 r0=0x8000 0000,那么執(zhí)行后 r0=0)

0x0800021A 0941 LSRS r1,r0,#5

這里是將 r0 右移 5 位后的結(jié)果存入 r1。因?yàn)榇藭r(shí) r0=32,即 0x0000 0020。右移 5 位后,即變成了 0x0000 0001,所以 r1 此時(shí)為 0x0000 0001

0x08000220 07C9 LSLS r1,r1,#31

這里是將 r1 左移 31 位,r1 此時(shí)就變成了 0x1000 0000, 這個值會觸發(fā)程序狀態(tài)寄存器自動更新一些標(biāo)志位,比如這里會自動更新后面會用到的 Z 標(biāo)志位,即零標(biāo)志位,因?yàn)榇藭r(shí) r1 里的值不為 0,那么 Z 標(biāo)志位就會被自動置為 0。

現(xiàn)在,就相當(dāng)于有了一個標(biāo)志位 Z 表示 buffer_full 這個變量為 0 了,后續(xù)的 BNE 其實(shí)就是判斷這個 Z 標(biāo)志位。

(通過 CLZ 數(shù)出前導(dǎo) 0 的個數(shù),先右移 5 位,再左移 31 位后,即可通過標(biāo)志位判斷變量是否為全 0。)


0x0800021C F04F30FF MOV r0, #0xFFFFFFFF

這里相當(dāng)于初始化代碼中的 count 值,也是為了方便后續(xù)操作。

0x08000222 F1000001 ADD r0,r0,#1

這里是將 count 對應(yīng)的寄存器 r0 加 1,對應(yīng)循環(huán)體中執(zhí)行的 count++ 操作。


0x08000226 F04F0101 MOV r1,#1

這里是將 r1 重新設(shè)置為 1,為下次循環(huán)使用。保證了下一次進(jìn)入循環(huán)時(shí),r1 是一個可控的初始值。因?yàn)橄乱粭l指令是 BNE,如果 BNE 判斷變量為 0,就要跳轉(zhuǎn)到 0x08000220 繼續(xù)循環(huán),而 0x08000220 地址需要執(zhí)行 r1 左移 31 位,所以這里是為了循環(huán)而考慮設(shè)置的指令。

0x0800022A D1F9 BNE 0x08000220

這里是,如果 BNE 通過 Z 標(biāo)志位判斷出 buffer_full 為 0,則跳轉(zhuǎn)回地址 0x08000220 繼續(xù)循環(huán)。


2)、加了volatile關(guān)鍵字時(shí),函數(shù)的反匯編表現(xiàn)如圖2所示:

圖 2

反匯編解讀:

0x0800020C F240110C MOVW r1, #0x10C

0x08000210 F2C20100 MOVT r1, #0x2000

這兩句指令將變量 buffer_full 的地址加載到寄存器 r1 中,此時(shí) r1= 0x200010C,后續(xù)方便直接從這個地址讀取數(shù)據(jù)。


0x08000210 F04F30FF MOV r0, #0xFFFFFFFF

這里相當(dāng)于初始化代碼中的 count 值,也是為了方便后續(xù)操作。


0x08000218 680A LDR r2, [r1, #0x00]

從 r1 存儲的內(nèi)存地址里 (即 buffer_full 的地址)讀取值,并保存值到寄存器 r2。即讀取 buffer_full 的當(dāng)前值。


0x0800021A 3001 ADDS r0, r0, #0x01

將 count 對應(yīng)的寄存器 r0 加 1,對應(yīng)循環(huán)體中執(zhí)行的 count++ 操作。


0x0800021C 2A00 CMP r2, #0x00

將 r2(即 buffer_full 的值)與 0 進(jìn)行比較。這里比較完 2 個數(shù)值后,也會更新程序狀態(tài)寄存器里的標(biāo)志位,其實(shí)也是 Z 標(biāo)志位,如果 2 個被比較的數(shù)值是相同的,Z 就會等于 1,Z=1 就會使下一條指令 BEQ 跳轉(zhuǎn)到指定的地址,去開啟新一輪的循環(huán)。


0x0800021E D0FB BEQ 0x08000218

通過判斷 Z 標(biāo)志位,來確定 buffer_full 是否為 0。

如果 buffer_full == 0,跳轉(zhuǎn)到地址 0x08000218(重新執(zhí)行循環(huán))。

如果 buffer_full != 0,跳出循環(huán),執(zhí)行后續(xù)邏輯。


3)、比較有無關(guān)鍵字時(shí)的編譯器行為

問 ①:很明顯可以看到,圖 1 中的代碼,不加 volatile 時(shí),反匯編代碼其實(shí)會長一些,為什么會多出來這么多指令呢?

答 ①:其實(shí)是因?yàn)椴患?volatile,編譯器就會假設(shè)?。。∷鼤僭O(shè)要判斷的變量值,在該函數(shù)運(yùn)行時(shí)不會改變,所以就會使用更少的寄存器(2 個寄存器),更簡單的位操作(即:導(dǎo)致代碼稍微增多的原因),來實(shí)現(xiàn)他認(rèn)為相同的判斷和循環(huán)功能。而加了 volatile 以后,他就會使用 3 個寄存器和略微高級的指令,來完成判斷和循環(huán)功能。



問 ②:那么為什么不加 volatile 會少用 1 個寄存器而增加一些匯編代碼,加了 volatile 會多用 1 個寄存器而減少匯編代碼呢?

答 ②:因?yàn)?Os 等級的優(yōu)化。Os 優(yōu)化等級會導(dǎo)致:編譯器要考慮提高執(zhí)行效率,因?yàn)檫@個 Os 優(yōu)化等級他是想要在減少代碼量和提高執(zhí)行效率上做權(quán)衡的。


所以,判斷變量是否為 0 這個操作,他能用 2 個寄存器,就用 2 個,不多占另外的寄存器,讓出空閑的寄存器給更復(fù)雜的功能,提高效率。

而且他增加的指令,都是最簡單的位操作,不是復(fù)雜的匯編指令,所以對于流水線的利用會更加充分,所以這里雖然相比于加了 volatile 后所增加的一點(diǎn)點(diǎn)代碼(其實(shí)相比于 O0 優(yōu)化等級,已經(jīng)減少了匯編代碼量),但是換來了更快的執(zhí)行速度,空出了更多的寄存器給接下來的功能使用,是一種非常有大局觀的權(quán)衡之策。


最后,加了 volatile 后,編譯器明顯變得謹(jǐn)慎了,他很害怕,因?yàn)樗兰恿?volatile 關(guān)鍵字的變量,是隨時(shí)可能被外界更改的,他不敢再進(jìn)行大量的位操作去判斷變量是否為 0 了,因?yàn)楹苡锌赡茉趫?zhí)行這些位操作時(shí),這個變量就被別的任務(wù)更改了,他必須在獲取到變量最新值時(shí),立馬執(zhí)行判斷,這樣才能獲取到正確的比較結(jié)果。


問 ③:加了 volatile 后,他本身帶來的好處顯現(xiàn)在哪里?

答 ③:圖 1 中,可以看到,他第一次判斷完 buffer_full 為 0 以后,就循環(huán)使用那個判斷結(jié)果,根本就不再從原始的地址取數(shù)對比了。而在圖 2 中,循環(huán)時(shí),函數(shù)很明顯跳回到了 LDR 指令那里,LDR 會從變量的內(nèi)存地址中重新取數(shù),確保每次使用的都是從內(nèi)存中取出的最新值。