查看Go源码输出的汇编代码

go tool compile -S hello.go

或
go tool objdump -s "main\.main" hello

或
go tool compile -N -S hello.go
最好用这个,这种是禁止优化的

小例子

只有一个返回值

[22:58:38] emacsist:Desktop $ cat hello.go
package main

func main() {
	a, b := 3, 4
	println(add(a, b))
}

func add(a, b int) int {
	return a + b
}

输出的汇编代码:

"".add t=1 size=19 args=0x18 locals=0x0
	0x0000 00000 (hello.go:8)	TEXT	"".add(SB), $0-24
	0x0000 00000 (hello.go:8)	FUNCDATA	$0, gclocals·54241e171da8af6ae173d69da0236748(SB)
	0x0000 00000 (hello.go:8)	FUNCDATA	$1, gclocals·33cdeccccebe80329f1fdbee7f5874cb(SB)
	0x0000 00000 (hello.go:9)	MOVQ	"".b+16(FP), AX
	0x0005 00005 (hello.go:9)	MOVQ	"".a+8(FP), CX
	0x000a 00010 (hello.go:9)	ADDQ	CX, AX
	0x000d 00013 (hello.go:9)	MOVQ	AX, "".~r2+24(FP)
	0x0012 00018 (hello.go:9)	RET
	0x0000 48 8b 44 24 10 48 8b 4c 24 08 48 01 c8 48 89 44  H.D$.H.L$.H..H.D
	0x0010 24 18 c3                                         $..

FP => 指向栈底(高位内存地址)(Frame pointer) SP => 指向栈顶(低位内存地址)(Stack pointer)

  • ”“.add => 表示命名空间,这就是上面源码中的 add 函数
  • t => 这个我也不知道表示什么意思。。。有知道的,麻烦告知一下 ^_^
  • size => 表示这个函数是 19 个字节
  • args => 表示参数总占字节数(16进制显示)
  • locals => 表示局部变量(即在函数内部的变量)的总字节数(16进制显示)
  • TEXT “”.add(SB), $0-24 => 这表示代码段(TEXT),SB(Static Base)表示函数 add 的地址,$0:表示局部变量总字节数(十进制显示), -24:表示参数总占字节数(十进制显示)(包括传入和返回参数的总字节数之和),因为这里一共3个 int, 3*8=24 . 可以这样子输出它的大小: fmt.Printf("a: %T, %d\n", a, unsafe.Sizeof(a)) ,我这里输出为: a: int, 8
  • FUNCDATA => 这个是与垃圾收集器相关的,这里不理它
  • MOVQ “”.b+16(FP), AX => MOVQ表示移动2个字(1字=2字节=16位,所以这里是32位,可以用一个32位的寄存器来保存,这里为AX)。即 FP + 16 (即是变量b的内存数据), FP + 8 (即是变量a的内存数据)。从这里可以推出,Golang中处理参数是从左到右顺序压入栈的。
  • ADDQ CX, AX => 前面已经将变量b的数据放到了 AX, 变量a的数据放到了CX, 这里就将它们相加,然后再保存到 AX 寄存器中。
  • MOVQ AX, “”.~r2+24(FP) => 将返回结果(结果保存到了AX)保存到栈的 FP+24 的位置。~r2 表示参数的位置(位置是按从函数左到右的参数顺序开始算起,包括返回参数。 ~r2 表示第2<位置从0开始> 个参数的值为 AX的值。从这里可以推出:返回值的存储地址比参数的存储地址高。
  • RET => 函数返回

多个返回值

package main

func main() {
	a, b, c := 3, 4, 5
	a1, s2 := add(a, b, c)
	println(a1, s2)
}

func add(a, b, c int) (int, int) {
	return a + b + c, a + b
}

汇编后的代码

"".add t=1 size=32 args=0x28 locals=0x0
	0x0000 00000 (hello.go:9)	TEXT	"".add(SB), $0-40
	0x0000 00000 (hello.go:9)	FUNCDATA	$0, gclocals·24b0aee1021c20d1590e75b99691b0e0(SB)
	0x0000 00000 (hello.go:9)	FUNCDATA	$1, gclocals·33cdeccccebe80329f1fdbee7f5874cb(SB)
	0x0000 00000 (hello.go:10)	MOVQ	"".b+16(FP), AX
	0x0005 00005 (hello.go:10)	MOVQ	"".a+8(FP), CX
	0x000a 00010 (hello.go:10)	ADDQ	CX, AX
	0x000d 00013 (hello.go:10)	MOVQ	"".c+24(FP), CX
	0x0012 00018 (hello.go:10)	ADDQ	AX, CX
	0x0015 00021 (hello.go:10)	MOVQ	CX, "".~r3+32(FP)
	0x001a 00026 (hello.go:10)	MOVQ	AX, "".~r4+40(FP)
	0x001f 00031 (hello.go:10)	RET
	0x0000 48 8b 44 24 10 48 8b 4c 24 08 48 01 c8 48 8b 4c  H.D$.H.L$.H..H.L
	0x0010 24 18 48 01 c1 48 89 4c 24 20 48 89 44 24 28 c3  $.H..H.L$ H.D$(.

意思和上面的说明大概相同。这里特别说明的是:

~r3 => 表示第3个参数的值(即返回值)(未命名的情况下) ~r4 => 表示第4个参数的值(即返回值)(未命名的情况下)

如果返回值有命名的话,它是这样子的:

func add(a, b, c int) (d int, e int) {
	return a + b + c, a + b
}

"".add t=1 size=32 args=0x28 locals=0x0
	0x0000 00000 (hello.go:9)	TEXT	"".add(SB), $0-40
	0x0000 00000 (hello.go:9)	FUNCDATA	$0, gclocals·24b0aee1021c20d1590e75b99691b0e0(SB)
	0x0000 00000 (hello.go:9)	FUNCDATA	$1, gclocals·33cdeccccebe80329f1fdbee7f5874cb(SB)
	0x0000 00000 (hello.go:10)	MOVQ	"".b+16(FP), AX
	0x0005 00005 (hello.go:10)	MOVQ	"".a+8(FP), CX
	0x000a 00010 (hello.go:10)	ADDQ	CX, AX
	0x000d 00013 (hello.go:10)	MOVQ	"".c+24(FP), CX
	0x0012 00018 (hello.go:10)	ADDQ	AX, CX
	0x0015 00021 (hello.go:10)	MOVQ	CX, "".d+32(FP)
	0x001a 00026 (hello.go:10)	MOVQ	AX, "".e+40(FP)
	0x001f 00031 (hello.go:10)	RET
	0x0000 48 8b 44 24 10 48 8b 4c 24 08 48 01 c8 48 8b 4c  H.D$.H.L$.H..H.L
	0x0010 24 18 48 01 c1 48 89 4c 24 20 48 89 44 24 28 c3  $.H..H.L$ H.D$(.

所以,Go 中多个返回值的实现就是这样子处理的。

函数调用图

func add(a, b, c int) (d int, e int) {
	var a1 int = 3
	var a2 int = a1 * 3
	return a + b + c + a2, a + b + a1
}

栈的改变

SP=SP-N,表示是开辟一个空间(进行函数调用)
SP=SP+N,表示释放一个空间 (函数调用完毕,返回)

低位地址
^
|                       
|                        add 函数局部变量2(a2) <------SP
|                        add 函数局部变量1(a1) 
|                        main.main 的返回地址  <------FP
|                        add 函数参数1(a)      
|                        add 函数参数2(b)
|                        add 函数参数3(c)
|                        add 函数参数返回值1(d)
|                        add 函数参数返回值2(e)
高位地址                  runtime.main函数返回地址

FP的空间(即所有函数参数和返回值的空间):它是在调用方进行分配的(即,传入和返回参数的空间,在这个例子里是在 main 函数中分配好的,它属于 main 所在栈的一部分)

SP的空间:这个才是 add 的栈

在Go中,每一个 Goroutine 都有它自己的栈空间。

下面是禁用优化后输出的汇编:

"".add t=1 size=151 args=0x28 locals=0x28
	0x0000 00000 (hello.go:9)	TEXT	"".add(SB), $40-40
	0x0000 00000 (hello.go:9)	SUBQ	$40, SP
	0x0004 00004 (hello.go:9)	MOVQ	BP, 32(SP)
	0x0009 00009 (hello.go:9)	LEAQ	32(SP), BP
	0x000e 00014 (hello.go:9)	FUNCDATA	$0, gclocals·24b0aee1021c20d1590e75b99691b0e0(SB)
	0x000e 00014 (hello.go:9)	FUNCDATA	$1, gclocals·33cdeccccebe80329f1fdbee7f5874cb(SB)
	0x000e 00014 (hello.go:9)	MOVQ	$0, "".d+72(FP)
	0x0017 00023 (hello.go:9)	MOVQ	$0, "".e+80(FP)
	0x0020 00032 (hello.go:9)	MOVQ	$0, "".e+80(FP)
	0x0029 00041 (hello.go:9)	MOVQ	$0, "".d+72(FP)
	0x0032 00050 (hello.go:10)	MOVQ	$3, "".a1+8(SP)
	0x003b 00059 (hello.go:11)	MOVQ	$9, "".a2(SP)
	0x0043 00067 (hello.go:12)	MOVQ	"".a+48(FP), AX
	0x0048 00072 (hello.go:12)	MOVQ	"".b+56(FP), CX
	0x004d 00077 (hello.go:12)	MOVQ	"".c+64(FP), DX
	0x0052 00082 (hello.go:12)	ADDQ	CX, AX
	0x0055 00085 (hello.go:12)	LEAQ	9(AX)(DX*1), AX
	0x005a 00090 (hello.go:12)	MOVQ	AX, ""..autotmp_3+24(SP)
	0x005f 00095 (hello.go:12)	MOVQ	"".a+48(FP), AX
	0x0064 00100 (hello.go:12)	MOVQ	"".b+56(FP), CX
	0x0069 00105 (hello.go:12)	MOVQ	"".a1+8(SP), DX
	0x006e 00110 (hello.go:12)	ADDQ	CX, AX
	0x0071 00113 (hello.go:12)	ADDQ	DX, AX
	0x0074 00116 (hello.go:12)	MOVQ	AX, ""..autotmp_4+16(SP)
	0x0079 00121 (hello.go:12)	MOVQ	""..autotmp_3+24(SP), AX
	0x007e 00126 (hello.go:12)	MOVQ	AX, "".d+72(FP)
	0x0083 00131 (hello.go:12)	MOVQ	""..autotmp_4+16(SP), AX
	0x0088 00136 (hello.go:12)	MOVQ	AX, "".e+80(FP)
	0x008d 00141 (hello.go:12)	MOVQ	32(SP), BP
	0x0092 00146 (hello.go:12)	ADDQ	$40, SP
	0x0096 00150 (hello.go:12)	RET

返回值设置零值:

	0x000e 00014 (hello.go:9)	MOVQ	$0, "".d+72(FP)
	0x0017 00023 (hello.go:9)	MOVQ	$0, "".e+80(FP)
	0x0020 00032 (hello.go:9)	MOVQ	$0, "".e+80(FP)
	0x0029 00041 (hello.go:9)	MOVQ	$0, "".d+72(FP)

参考资料