1. 程式人生 > 程式設計 >彙編分析 Golang 迴圈(推薦)

彙編分析 Golang 迴圈(推薦)

女主宣言

今天小編為大家分享一篇關於Golang迴圈彙編分析的文章,文章中介紹了golang迴圈的彙編層面的處理,通過分析,我們可以更瞭解迴圈的實現。希望能對大家有所幫助。

PS:豐富的一線技術、多元化的表現形式,盡在“ 3 60雲端計算 ”,點關注哦!

迴圈是程式設計中很強大的一個概念,而且非常容易處理。 但是,必須將其翻譯成機器可理解的基本指令。 它的編譯方式也可能影響標準庫中的其他元件。 讓我們開始分析一下範圍迴圈 。

1迴圈彙編

範圍迴圈可以迭代陣列,切片或通道。下面函式展示了,對分片進行迴圈並將數字相加:

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 可以轉儲生成彙編程式碼,下面為範圍迴圈的相關程式碼。

0x0041 00065 (main.go:4) XORL AX,AX
0x0043 00067 (main.go:4) XORL CX,CX

0x0045 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)

我們把指令分為兩部分:初始化及迴圈本身。最開始兩行指令用來初始化兩個暫存器為0。

0x0041 00065 (main.go:4) XORL AX,CX

暫存器AX包含迴圈中的當前位置,而CX包含變數 t 的值。下面是帶有指令和通用暫存器的直觀表示:

彙編分析 Golang 迴圈(推薦)

該迴圈指令 JMP 82 開始,表示跳轉到指令82。可以通過第二列來標識此目標指令:

下一條指令 CMPQ AX,$5 表示“比較暫存器AX和數值5”。它實際上是從AX中減去暫存器DX的值,並將結果儲存到另一個暫存器中。現在,可以在下一條指令JLT 71中使用該值,該指令表示“如果小於0,則跳轉到指令71。”下面是更新後的圖:

彙編分析 Golang 迴圈(推薦)

如果條件不滿足,則程式將不會跳轉執行迴圈後面的下一條指令。

因此,我們現在有了迴圈的結構。下面是轉換回 Go 的迴圈:

goto end
start:
 ?
end:
 if i < 5 {
  goto start
 }

println(t)

該迴圈的主體是缺失的,下面是指令:

0x0047 00071 (main.go:7) MOVQ ""..autotmp_5+16(SP)(AX*8),CX

第一個指令 MOVQ ""..autotmp_5+16(SP)(AX*8),DX 表示“將記憶體從源移動到目標”。由以下內容組成:

  • 片段""..autotmp_5+16(SP) 其中 SP 是堆疊指標(我們當前的記憶體棧幀),而 autotmp_* 是自動生成的變數名稱。
  • 偏移量8(在64位架構上,int為8位)乘以暫存器AX的值,即迴圈中的當前位置。
  • 由暫存器DX表示的,目標現在包含迴圈的當前值。

然後,INCQ 代表“遞增”,並將遞增迴圈的當前位置:

彙編分析 Golang 迴圈(推薦)

迴圈體的最後一條指令是 ADDQ DX,CX 表示“將DX新增到CX”。之前我們已經看到DX包含迴圈的當前值,而CX是包含變數 t 內容的暫存器:

彙編分析 Golang 迴圈(推薦)

它將一直迴圈直到迴圈計數器到達5。然後,迴圈之後的指令顯示暫存器CX將其值移至 t :

0x0058 00088 (main.go:11) MOVQ CX,"".t+8(SP)

這是處於最終狀態的圖:

彙編分析 Golang 迴圈(推薦)

我們還可以在Go中完成迴圈的翻譯:

func main() {
 l := []int{9,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)
}

為這個新程式生成彙編程式碼,將提供完全相同的輸出。

2改進

內部轉換迴圈的方式可能會對其他功能(例如Go排程程式)產生影響。在Go 1.10之前,編譯的迴圈類似於以下程式碼:

func main() {
 l := []int{9,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)
}

這種實現方式的問題是,當達到5時,指標p超過了分配的末尾。這個問題使迴圈不容易被搶佔,因為它的主體不安全。迴圈編譯的優化確保它不會建立任何過去的指標。為準備Go排程程式中的非合作式搶佔而進行了此改進。

總結

以上所述是小編給大家介紹的彙編分析 Golang 迴圈,希望對大家有所幫助,也非常感謝大家對我們網站的支援!