概述
对于函数调用,Go 语言使用调用者预先分配的栈来传递参数和返回值,使得多值返回成为可能。
以下基于 Go1.14
考虑以下代码: 1
2
3
4
5
6
7
8
9package main
func do(a, b int) (int, bool) {
return a + b, a == b
}
func main() {
do(33, 66)
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26$ go tool compile -S -N -l hello.go
....
"".main STEXT size=68 args=0x0 locals=0x28
0x0000 00000 (hello.go:7) TEXT "".main(SB), ABIInternal, $40-0
0x0000 00000 (hello.go:7) MOVQ (TLS), CX
0x0009 00009 (hello.go:7) CMPQ SP, 16(CX)
0x000d 00013 (hello.go:7) PCDATA $0, $-2
0x000d 00013 (hello.go:7) JLS 61
0x000f 00015 (hello.go:7) PCDATA $0, $-1
0x000f 00015 (hello.go:7) SUBQ $40, SP # 分配 40 字节的栈空间
0x0013 00019 (hello.go:7) MOVQ BP, 32(SP) # 保存基址指针 BP 到栈上
0x0018 00024 (hello.go:7) LEAQ 32(SP), BP # 修改基址指针
0x001d 00029 (hello.go:7) PCDATA $0, $-2
0x001d 00029 (hello.go:7) PCDATA $1, $-2
0x001d 00029 (hello.go:7) FUNCDATA $0, gclocals·33cdeccccebe80329f1fdbee7f5874cb(SB)
0x001d 00029 (hello.go:7) FUNCDATA $1, gclocals·33cdeccccebe80329f1fdbee7f5874cb(SB)
0x001d 00029 (hello.go:7) FUNCDATA $2, gclocals·33cdeccccebe80329f1fdbee7f5874cb(SB)
0x001d 00029 (hello.go:8) PCDATA $0, $0
0x001d 00029 (hello.go:8) PCDATA $1, $0
0x001d 00029 (hello.go:8) MOVQ $33, (SP) # 第一个参数
0x0025 00037 (hello.go:8) MOVQ $66, 8(SP) # 第二个参数
0x002e 00046 (hello.go:8) CALL "".do(SB) # 将当前的 IP 压入栈中,然后调用函数 do
0x0033 00051 (hello.go:9) MOVQ 32(SP), BP # 恢复基址指针
0x0038 00056 (hello.go:9) ADDQ $40, SP # 回收分配的栈空间
0x003c 00060 (hello.go:9) RET
......
接着通过 CALL
指令,将 main
的返回地址压入栈中,然后进行函数调用。 1
2
3
4
5
6
7
8
9
10
11
12"".do STEXT nosplit size=45 args=0x20 locals=0x0
......
0x0000 00000 (hello.go:3) MOVQ $0, "".~r2+24(SP) # 初始化第一个返回值
0x0009 00009 (hello.go:3) MOVB $0, "".~r3+32(SP) # 初始化第二个返回值
0x000e 00014 (hello.go:4) MOVQ "".a+8(SP), AX # 获取第一个参数,AX = 33
0x0013 00019 (hello.go:4) ADDQ "".b+16(SP), AX # 做加法,AX = AX + 66 = 99
0x0018 00024 (hello.go:4) MOVQ AX, "".~r2+24(SP) # 把计算结果保存在第一个返回值中,24(SP) = AX = 99
0x001d 00029 (hello.go:4) MOVQ "".b+16(SP), AX # AX = 66
0x0022 00034 (hello.go:4) CMPQ "".a+8(SP), AX # 8(SP) 和 AX 进行比较,即 AX - 8(SP) = 66 - 33 = 33
0x0027 00039 (hello.go:4) SETEQ "".~r3+32(SP) # 判断上一步的计算结果是否为 0,保存在第二个返回值中,32(SP) = 0
0x002c 00044 (hello.go:4) RET # 修改 IP,返回调用点
......
参数传递
变长参数
将 do
函数修改为支持变长参数,如下所示: 1
2
3
4
5
6
7
8func do(nums ...int) {
fmt.Printf("%T %v\n", nums, nums)
}
func main() {
do() // output: []int []
do(33, 44, 55) // output: []int [33 44 55]
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41"".main STEXT size=196 args=0x0 locals=0x40
……
# do(33, 44, 55)
# 创建一个大小为 3,容量为 3 的 slice,值为[33, 44, 55]
0x0036 00054 (hello.go:11) LEAQ type.[3]int(SB), AX
0x003d 00061 (hello.go:11) PCDATA $0, $0
0x003d 00061 (hello.go:11) MOVQ AX, (SP)
0x0041 00065 (hello.go:11) CALL runtime.newobject(SB)
0x0046 00070 (hello.go:11) PCDATA $0, $1
0x0046 00070 (hello.go:11) MOVQ 8(SP), AX
0x004b 00075 (hello.go:11) PCDATA $1, $1
0x004b 00075 (hello.go:11) MOVQ AX, ""..autotmp_1+24(SP)
0x0050 00080 (hello.go:11) PCDATA $0, $0
0x0050 00080 (hello.go:11) MOVQ $33, (AX)
0x0057 00087 (hello.go:11) PCDATA $0, $1
0x0057 00087 (hello.go:11) MOVQ ""..autotmp_1+24(SP), AX
0x005c 00092 (hello.go:11) TESTB AL, (AX)
0x005e 00094 (hello.go:11) PCDATA $0, $0
0x005e 00094 (hello.go:11) MOVQ $44, 8(AX)
0x0066 00102 (hello.go:11) PCDATA $0, $1
0x0066 00102 (hello.go:11) MOVQ ""..autotmp_1+24(SP), AX
0x006b 00107 (hello.go:11) TESTB AL, (AX)
0x006d 00109 (hello.go:11) PCDATA $0, $0
0x006d 00109 (hello.go:11) MOVQ $55, 16(AX)
0x0075 00117 (hello.go:11) PCDATA $0, $1
0x0075 00117 (hello.go:11) PCDATA $1, $0
0x0075 00117 (hello.go:11) MOVQ ""..autotmp_1+24(SP), AX
0x007a 00122 (hello.go:11) TESTB AL, (AX)
0x007c 00124 (hello.go:11) JMP 126
0x007e 00126 (hello.go:11) MOVQ AX, ""..autotmp_0+32(SP)
0x0083 00131 (hello.go:11) MOVQ $3, ""..autotmp_0+40(SP)
0x008c 00140 (hello.go:11) MOVQ $3, ""..autotmp_0+48(SP)
0x0095 00149 (hello.go:11) PCDATA $0, $0
0x0095 00149 (hello.go:11) MOVQ AX, (SP)
0x0099 00153 (hello.go:11) MOVQ $3, 8(SP)
0x00a2 00162 (hello.go:11) MOVQ $3, 16(SP)
# 调用 do 函数
0x00ab 00171 (hello.go:11) CALL "".do(SB)
0x00b0 00176 (hello.go:12) MOVQ 56(SP), BP
0x00b5 00181 (hello.go:12) ADDQ $64, SP
0x00b9 00185 (hello.go:12) RETdo
的返回值: 1
2
3func do(a, b int) (res int, equal bool) {
return a + b, a == b
}main
在进行函数调用的时候基本没有改动,但是被调用函数在执行的时候多了几项操作。在执行前,会申请额外的栈空间来存放临时返回值。然后,初始化命名返回值,进行一系列操作后,把返回结果保存在临时返回值中。最后,再使用临时返回值一一设置命名返回值。接着释放存放临时返回值的栈空间,返回到函数调用点。 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27"".do STEXT nosplit size=87 args=0x20 locals=0x18
0x0000 00000 (hello.go:3) TEXT "".do(SB), NOSPLIT|ABIInternal, $24-32
0x0000 00000 (hello.go:3) SUBQ $24, SP // 申请空间存放临时返回值
0x0004 00004 (hello.go:3) MOVQ BP, 16(SP)
0x0009 00009 (hello.go:3) LEAQ 16(SP), BP
0x000e 00014 (hello.go:3) PCDATA $0, $-2
0x000e 00014 (hello.go:3) PCDATA $1, $-2
0x000e 00014 (hello.go:3) FUNCDATA $0, gclocals·33cdeccccebe80329f1fdbee7f5874cb(SB)
0x000e 00014 (hello.go:3) FUNCDATA $1, gclocals·33cdeccccebe80329f1fdbee7f5874cb(SB)
0x000e 00014 (hello.go:3) FUNCDATA $2, gclocals·33cdeccccebe80329f1fdbee7f5874cb(SB)
0x000e 00014 (hello.go:3) PCDATA $0, $0
0x000e 00014 (hello.go:3) PCDATA $1, $0
0x000e 00014 (hello.go:3) MOVQ $0, "".res+48(SP) // 初始化第一个返回值,res = 0
0x0017 00023 (hello.go:3) MOVB $0, "".equal+56(SP) // 初始化第二个返回值,equal = 0
0x001c 00028 (hello.go:4) MOVQ "".a+32(SP), AX // AX = a = 33
0x0021 00033 (hello.go:4) ADDQ "".b+40(SP), AX // AX = AX + b = 33 + 66 = 99
0x0026 00038 (hello.go:4) MOVQ AX, ""..autotmp_4+8(SP) // 计算结果保存在临时变量中
0x002b 00043 (hello.go:4) MOVQ "".b+40(SP), AX
0x0030 00048 (hello.go:4) CMPQ "".a+32(SP), AX
0x0035 00053 (hello.go:4) SETEQ ""..autotmp_5+7(SP) // 计算结果保存在临时变量中
0x003a 00058 (hello.go:4) MOVQ ""..autotmp_4+8(SP), AX
0x003f 00063 (hello.go:4) MOVQ AX, "".res+48(SP) // 用临时变量设置命名返回值
0x0044 00068 (hello.go:4) MOVBLZX ""..autotmp_5+7(SP), AX // 用临时变量设置命名返回值
0x0049 00073 (hello.go:4) MOVB AL, "".equal+56(SP)
0x004d 00077 (hello.go:4) MOVQ 16(SP), BP
0x0052 00082 (hello.go:4) ADDQ $24, SP
0x0056 00086 (hello.go:4) RET1
2
3
4func do(a, b int) (int, bool) {
r1, r2 := a+b, a == b
return r1, r2
}1
2
3
4
5
6func do(a, b int) (res int, equal bool) {
tmp1, tmp2 := a+b, a == b
res = tmp1
equal = tmp2
return res, equal
}