Go 中的循環(huán)是如何轉(zhuǎn)為匯編的(方法詳解)
本文基于 Go 1.13 版本
循環(huán)在編程中是一個重要的概念,且易于上手。但是,循環(huán)必須被翻譯成計算機能理解的底層指令。它的編譯方式也會在一定程度上影響到標準庫中的其他組件。讓我們開始分析循環(huán)吧。
循環(huán)的匯編代碼
使用循壞迭代 array
, slice
, channel
,以下是一個使用循環(huán)對 slice
計算總和的例子。
func main() { l := []int{9, 45, 23, 67, 78} t := 0 for _, v := range l { t += v } println(t) }
使用 go tool compile -S main.go
生成的匯編代碼,以下為相關(guān)輸出:
0x0041 00065 (main.go:4) XORL AX, AX
0x0043 00067 (main.go:4) XORL CX, CX0x0045 00069 (main.go:7) JMP 82
0x0047 00071 (main.go:7) MOVQ ""..autotmp_5+16(SP)(AX*8), DX
0x004c 00076 (main.go:7) INCQ AX
0x004f 00079 (main.go:8) ADDQ DX, CX
0x0052 00082 (main.go:7) CMPQ AX, $5
0x0056 00086 (main.go:7) JLT 71
0x0058 00088 (main.go:11) MOVQ CX, "".t+8(SP)
我把這些指令分為了兩個部分,初始化部分和循環(huán)主體。前兩條指令,將兩個寄存器初始化為零值。
0x0041 00065 (main.go:4) XORL AX, AX
0x0043 00067 (main.go:4) XORL CX, CX
寄存器 AX
包含著當前循環(huán)所處位置,而 CX
包含著變量 t
的值,下面為帶有指令和通用寄存器的直觀表示:
循環(huán)從表示「跳轉(zhuǎn)到指令 82 」的 JMP 82
開始,這條指令的作用可以通過第二行來判斷:
接下來的指令 CMPQ AX,$5
表示「比較寄存器 AX
和 5
」,事實上,這個操作是把 AX
中的值減去 5 ,然后儲存在另一個寄存器中,這個值可以被用在下一條指令 JLT 71
中,它的含義是 「如果值小于 0 則跳轉(zhuǎn)到指令 71 」,以下是更新后的直觀表示:
如果不滿足條件,則程序?qū)D(zhuǎn)到循環(huán)體之后的下一條指令執(zhí)行。
所以,我們現(xiàn)在有了對循環(huán)的基本框架,以下是轉(zhuǎn)換后的 Go 循環(huán):
goto end start: ? end: if i < 5 { goto start } println(t)
我們?nèi)鄙倭搜h(huán)的主體,接下來,我們看看這部分的指令:
0x0047 00071 (main.go:7) MOVQ ""..autotmp_5+16(SP)(AX*8), DX
0x004c 00076 (main.go:7) INCQ AX
0x004f 00079 (main.go:8) ADDQ DX, CX
第一條指令 MOVQ ""..autotmp_5+16(SP)(AX*8), DX
表示 「將內(nèi)存從源位置移動到目標地址」,它由以下幾個部分組成:
""..autotmp_5+16(SP)
表示 slice
,而 SP
表示了棧指針即我們當前的內(nèi)存空間, autotmp_*
是自動生成變量名。
- 偏差為 8 是因為在 64 位計算機架構(gòu)中,
int
類型是 8 字節(jié)的。 - 偏差乘以寄存器
AX
的值,表示當前循環(huán)中的位置。 - 寄存器
DX
代表的目標地址內(nèi)包含著循環(huán)的當前值。
之后, INCQ
表示自增,然后會增加循環(huán)的當前位置:
循環(huán)主體的最后一條指令是 ADDQ DX, CX
,表示把 DX
的值加在 CX
,所以我們可以看出, DX
所包含的值是目前循環(huán)所代表的的值,而 CX
代表了變量 t
的值。
他會一直循環(huán)至計數(shù)器到 5 ,之后循環(huán)體之后的指令表示為將寄存器 CX
的值賦予 t
:
0x0058 00088 (main.go:11) MOVQ CX, "".t+8(SP)
以下為最終狀態(tài)的示意圖:
我們可以完善 Go 中循環(huán)的轉(zhuǎn)換:
func main() { l := []int{9, 45, 23, 67, 78} t := 0 i := 0 var tmp int goto end start: tmp = l[i] i++ t += tmp end: if i < 5 { goto start } println(t) }
這個程序生成的匯編代碼與上文所提到的函數(shù)生成的匯編代碼有著相同的輸出。
改進
循環(huán)的內(nèi)部轉(zhuǎn)換方式可能會對其他特性(如 Go 調(diào)度器)產(chǎn)生影響。在 Go 1.10 之前,循環(huán)像下面的代碼一樣編譯:
func main() { l := []int{9, 45, 23, 67, 78} t := 0 i := 0 var tmp int p := uintptr(unsafe.Pointer(&l[0])) if i >= 5 { goto end } body: tmp = *(*int)(unsafe.Pointer(p)) p += unsafe.Sizeof(l[0]) i++ t += tmp if i < 5 { goto body } end: println(t) }
這種實現(xiàn)方式的問題是,當 i
達到 5 時,指針 p
已經(jīng)超過了內(nèi)存分配空間的尾部。這個問題使得循環(huán)不容易搶占,因為它的主體是不安全的。循環(huán)編譯的優(yōu)化確保它不會創(chuàng)建任何越界的指針。這個改進是為 Go 調(diào)度器中的非合作搶占做準備的。你可以在這篇Proposal 中到更詳細的討論。
到此這篇關(guān)于Go 中的循環(huán)是如何轉(zhuǎn)為匯編的(方法詳解)的文章就介紹到這了,更多相關(guān)go 循環(huán)匯編內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
UEFI開發(fā)實戰(zhàn)用戶交互界面使用說明VFR文件
這篇文章主要為大家介紹了UEFI開發(fā)實戰(zhàn)用戶交互界面使用說明VFR文件,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2022-06-06淺析ELF轉(zhuǎn)二進制允許把 Binary 文件加載到任意位置
本文通過 eip + 偏移地址 實現(xiàn)了運行時計算數(shù)據(jù)地址,不再需要把 Binary 文件裝載到固定的位置。本文通過實例代碼給大家介紹的非常詳細,需要的朋友參考下吧2020-02-02匯編語言進制轉(zhuǎn)換之16進制轉(zhuǎn)10進制
這篇文章主要介紹了匯編語言進制轉(zhuǎn)換之16進制轉(zhuǎn)10進制,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2020-07-07