亚洲国产成人av在线观看,777米奇色狠狠狠888影视,一二三四在线视频观看社区,小荡货奶真大水真多紧视频

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

1、 問(wèn)題場(chǎng)景

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


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

1)、軟件版本:MDK5.40

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

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


3、解決方法

1)、沒(méi)有加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ù)方便直接從這個(gè)地址讀取數(shù)據(jù)。

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

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


0x08000216 FAB0F080 CLZ r0,r0

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

(CLZ 指令會(huì)從最高有效位(最左側(cè)的位)開(kāi)始計(jì)算連續(xù)的零。對(duì)于全零的值,所有 32 位都是零的,會(huì)輸出 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, 這個(gè)值會(huì)觸發(fā)程序狀態(tài)寄存器自動(dòng)更新一些標(biāo)志位,比如這里會(huì)自動(dòng)更新后面會(huì)用到的 Z 標(biāo)志位,即零標(biāo)志位,因?yàn)榇藭r(shí) r1 里的值不為 0,那么 Z 標(biāo)志位就會(huì)被自動(dòng)置為 0。

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

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


0x0800021C F04F30FF MOV r0, #0xFFFFFFFF

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

0x08000222 F1000001 ADD r0,r0,#1

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


0x08000226 F04F0101 MOV r1,#1

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

0x0800022A D1F9 BNE 0x08000220

這里是,如果 BNE 通過(guò) 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ù)方便直接從這個(gè)地址讀取數(shù)據(jù)。


0x08000210 F04F30FF MOV r0, #0xFFFFFFFF

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


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

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


0x0800021A 3001 ADDS r0, r0, #0x01

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


0x0800021C 2A00 CMP r2, #0x00

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


0x0800021E D0FB BEQ 0x08000218

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

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

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


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

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

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



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

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


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

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


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


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

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