1. 程式人生 > 程式設計 >通過彙編看golang函式的多返回值問題

通過彙編看golang函式的多返回值問題

golang這門語言,有個比較好的特性,就是支援函式的多返回值。想C,C++,Java等這些語言,是不支援函式多返回的。但是C,C++可以使用傳遞指標,實現函式多返回。但是,你有沒有想過,golang是怎樣實現函式多返回值的呢?

我們知道,C,C++是通過暫存器實現函式返回值的,也就是先把返回值寫入到一個暫存器中,然後再從暫存器中,讀到函式的返回值。golang也是這樣實現的嗎?

偉大的思想家孔子曾說過,在原始碼面前一切都如同裸奔。後來,魯迅先生,總結了孔子的思想,說出了,在彙編面前,一切語法都是紙老虎。

下面我們通過golang的彙編指令,來看一下golang是怎樣實現函式的多返回值的

在看彙編之前,我們先用go的 debug

函式看下函式的棧資訊

程式碼很簡單,不用解釋了

package main
import (
 "fmt"
 "runtime/debug"
)

func main() {
 one(3)
}

func one(a int) (int,int) {
 fmt.Println(string(debug.Stack()))
 return a,a + 5
}

通過彙編看golang函式的多返回值問題

我標紅的這一行,就是 one 函式的棧資訊,第一個引數 0x3 很好理解,就是我們傳入的引數 3

, 但是後面這兩個是啥?還有,我明明只傳了一個引數,為啥會傳入三個引數?

到這裡,我也就不賣關子了,直接說了,後面這兩個引數,就是one函式返回值的地址,也就是說,one函式返回值地址不在one函式中,而是在呼叫one函式的mian函式中。golang的函式返回值,和C,C++的不同,golang的返回值是通過棧內地址實現的(返回值的地址是由函式呼叫者提供)。

package main

func main() {
 var b,c *int
 one(3,b,c)
}

func one(a int,c *int) {
}

也就是說,剛開始的那段程式碼,和這段在功能實現上,沒有什麼差別,只是golang編譯器提供的一個語法糖。

下面通過彙編來看一下

這次我們不是對深入分析golang的彙編,只是從彙編層面,驗證我們之前結論(golang函式多返回問題)

所以,不會死磕plan9彙編語法,說實話,plan9的很多知識我也不懂,大學沒開過彙編的課程,這些東西都是因為興趣自學的。

golang用的是plan9彙編,看plan9之前,先了解一下plan9的幾個概念

go彙編中有4個偽暫存器

  • FP: Frame pointer,指向棧底位置,一般用來引用函式的輸入引數,用來訪問函式的引數
  • PC: Program counter: 程式計數器,用於分支和跳轉
  • SB: Static base pointer: 一般用於宣告函式或者全域性變數
  • SP: Stack pointer:指向當前棧幀的區域性變數的開始位置(棧頂位置),一般用來引用函式的區域性變數

我們用這段程式碼進行彙編

package main

func main() {
 one(3)
}

func one(a int) (int,int) {
 return a,a + 5
}

使用 go tool compile -N -l -S main.go 得到彙編程式碼

"".main STEXT nosplit size=2 args=0x0 locals=0x0
  0x0000 00000 (C:\Users\bruce\Desktop\go\main.go:3)  TEXT "".main(SB),NOSPLIT|ABIInternal,$0-0
  0x0000 00000 (C:\Users\bruce\Desktop\go\main.go:3)  FUNCDATA  $0,gclocals·33cdeccccebe80329f1fdbee7f5874cb(SB)
  0x0000 00000 (C:\Users\bruce\Desktop\go\main.go:3)  FUNCDATA  $1,gclocals·33cdeccccebe80329f1fdbee7f5874cb(SB)
  0x0000 00000 (C:\Users\bruce\Desktop\go\main.go:3)  FUNCDATA  $3,gclocals·33cdeccccebe80329f1fdbee7f5874cb(SB)
  0x0000 00000 (C:\Users\bruce\Desktop\go\main.go:4)  PCDATA $2,$0
  0x0000 00000 (C:\Users\bruce\Desktop\go\main.go:4)  PCDATA $0,$0
  0x0000 00000 (C:\Users\bruce\Desktop\go\main.go:4)  XCHGL AX,AX
  0x0001 00001 (<unknown line number>) RET
  0x0000 90 c3           ..
"".one STEXT nosplit size=20 args=0x18 locals=0x0
  0x0000 00000 (C:\Users\bruce\Desktop\go\main.go:7)  TEXT "".one(SB),$0-24
  0x0000 00000 (C:\Users\bruce\Desktop\go\main.go:7)  FUNCDATA  $0,gclocals·33cdeccccebe80329f1fdbee7f5874cb(SB)
  0x0000 00000 (C:\Users\bruce\Desktop\go\main.go:7)  FUNCDATA  $1,gclocals·33cdeccccebe80329f1fdbee7f5874cb(SB)
  0x0000 00000 (C:\Users\bruce\Desktop\go\main.go:7)  FUNCDATA  $3,gclocals·33cdeccccebe80329f1fdbee7f5874cb(SB)
  0x0000 00000 (C:\Users\bruce\Desktop\go\main.go:8)  PCDATA $2,$0
  0x0000 00000 (C:\Users\bruce\Desktop\go\main.go:8)  PCDATA $0,$0
  0x0000 00000 (C:\Users\bruce\Desktop\go\main.go:8)  MOVQ "".a+8(SP),AX
  0x0005 00005 (C:\Users\bruce\Desktop\go\main.go:8)  MOVQ AX,"".~r1+16(SP)
  0x000a 00010 (C:\Users\bruce\Desktop\go\main.go:8)  ADDQ $5,AX
  0x000e 00014 (C:\Users\bruce\Desktop\go\main.go:8)  MOVQ AX,"".~r2+24(SP)
  0x0013 00019 (C:\Users\bruce\Desktop\go\main.go:8)  RET
  0x0000 48 8b 44 24 08 48 89 44 24 10 48 83 c0 05 48 89 H.D$.H.D$.H...H.
  0x0010 44 24 18 c3          D$..

我只截取了和main,one函式相關的部分

TEXT "".one(SB),$0-24 這行最後, $0-24 的含義,0代表one函式的棧幀大小(區域性變數+可能需要的額外呼叫函式的引數空間的總大小),因為one函式中沒有額外開銷,所有大小是0,24是傳入引數和返回值的大小,單位是位元組。傳入的引數和返回值都是 int ,在64位機器上,大小是8個位元組,64位。

通過彙編看golang函式的多返回值問題

簡單畫一下棧的示意圖

看一下這句 0x0000 00000 (C:\Users\bruce\Desktop\go\main.go:8) MOVQ "".a+8(SP),AX

SP暫存器指向的是棧頂的位置, AX 是一個通用暫存器

MOVQ指令 把 引數a 也就是(SP+8)的值搬到AX中

0x0005 00005 (C:\Users\bruce\Desktop\go\main.go:8) MOVQ AX,"".~r1+16(SP)
同樣,把AX中的值搬到r1(返回值b)

0x000a 00010 (C:\Users\bruce\Desktop\go\main.go:8) ADDQ $5,AX
ADDQ 指令把AX值+5

0x000e 00014 (C:\Users\bruce\Desktop\go\main.go:8) MOVQ AX,"".~r2+24(SP)
最後把AX的值搬到r2(返回值c)

0x0013 00019 (C:\Users\bruce\Desktop\go\main.go:8) RET
最後RET指令,one函式結束

總結

通過對golang進行彙編,真實了之前的結論

golang函式的多返回值不是通過暫存器傳遞,使用過使用呼叫值提供的地址,賦值實現的

先寫這些吧,我也是剛接觸golang的彙編,文中如有不正確的地方,還請指出

到此這篇關於通過彙編看golang函式的多返回值的文章就介紹到這了,更多相關彙編golang函式多返回值內容請搜尋我們以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援我們!