go 1.17之前
go1.17之前是用栈来传递参数的,下面实验证明,实验环境为
go version go1.14.2 windows/amd64
实验代码为
package main
func main() {
add(100, 200)
}
func add(a, b int) int {
c := a + b
return c
}
经过go tool compil -N -S -l main.go
后得到输出如下,删减了一些无关代码
// main 函数
0x0016 00022 (main1.go:3) SUBQ $32, SP
0x001a 00026 (main1.go:3) MOVQ BP, 24(SP)
0x001f 00031 (main1.go:3) LEAQ 24(SP), BP
0x0024 00036 (main1.go:4) MOVQ $100, (SP)
0x002c 00044 (main1.go:4) MOVQ $200, 8(SP)
0x0035 00053 (main1.go:4) CALL "".add(SB)
0x003a 00058 (main1.go:5) MOVQ 24(SP), BP
0x003f 00063 (main1.go:5) ADDQ $32, SP
0x0043 00067 (main1.go:5) RET
// add 函数
0x0000 00000 (main1.go:7) SUBQ $16, SP
0x0004 00004 (main1.go:7) MOVQ BP, 8(SP)
0x0009 00009 (main1.go:7) LEAQ 8(SP), BP
0x000e 00014 (main1.go:7) MOVQ $0, "".~r2+40(SP)
0x0017 00023 (main1.go:8) MOVQ "".a+24(SP), AX
0x001c 00028 (main1.go:8) ADDQ "".b+32(SP), AX
0x0021 00033 (main1.go:8) MOVQ AX, "".c(SP)
0x0025 00037 (main1.go:9) MOVQ AX, "".~r2+40(SP)
0x002a 00042 (main1.go:9) MOVQ 8(SP), BP
0x002f 00047 (main1.go:9) ADDQ $16, SP
0x0033 00051 (main1.go:9) RET
接下来按照代码的执行顺序,画出对应的栈图。代码顺序大致为
main函数栈空间分配
0x0016 00022 (main1.go:3) SUBQ $32, SP // 申请了32字节大小的栈空间
0x001a 00026 (main1.go:3) MOVQ BP, 24(SP) // 保存BP
0x001f 00031 (main1.go:3) LEAQ 24(SP), BP // 设置当前栈帧的栈底
这三行代码的作用就是申请栈空间,并保留BP寄存器的值,执行完之后,当前栈中数据为
main调用add函数
0x0024 00036 (main1.go:4) MOVQ $100, (SP)
0x002c 00044 (main1.go:4) MOVQ $200, 8(SP)
0x0035 00053 (main1.go:4) CALL "".add(SB)
前两行代码的作用就是将传递给add函数的100,和200参数值放到栈中,参数顺序是按照函数声明从左到右,从下到上存储,然后通过CALL指令调用add函数,此时栈中数据为
add函数栈空间分配
0x0000 00000 (main1.go:7) SUBQ $16, SP
0x0004 00004 (main1.go:7) MOVQ BP, 8(SP)
0x0009 00009 (main1.go:7) LEAQ 8(SP), BP
add函数栈空间分配和前面是一样的,add函数申请了16字节的栈空间,此时栈中数据如下图所示(注:个人认为函数栈空间应该仅仅包含通过SUBQ
指令获取的栈空间,但是这个返回地址并没有显式声明在caller和callee的栈空间里面,如果有知道具体的朋友还请不吝赐教)
此时的SP+16到SP+48这段栈空间是属于main函数的,而SP到SP+16是add函数的栈空间的,而中间的sp+16到sp+24这段栈空间存储的是返回地址,用于add函数返回时,跳转回main函数
add函数计算
0x000e 00014 (main1.go:7) MOVQ $0, "".~r2+40(SP) // 返回值初始化
0x0017 00023 (main1.go:8) MOVQ "".a+24(SP), AX // 参数a处理
0x001c 00028 (main1.go:8) ADDQ "".b+32(SP), AX // 参数b,执行加法运算
0x0021 00033 (main1.go:8) MOVQ AX, "".c(SP) // 将运算结果存储到变量c中
0x0025 00037 (main1.go:9) MOVQ AX, "".~r2+40(SP) // 将运算结果放到返回值中
这5行汇编代码就是add函数的主体了,add函数执行完成后栈空间数据为
sp位置就是变量c,而sp+40即为返回值的地址,如果main函数中接收了add函数的返回值,就会从sp+40这个位置取值,汇编代码为MOVQ 40(sp), AX
add函数返回
0x002a 00042 (main1.go:9) MOVQ 8(SP), BP
0x002f 00047 (main1.go:9) ADDQ $16, SP
0x0033 00051 (main1.go:9) RET
这三行代码对应于之前分配栈空间的代码,恢复BP寄存器,释放之前申请的16字节栈空间
main函数返回
0x003a 00058 (main1.go:5) MOVQ 24(SP), BP
0x003f 00063 (main1.go:5) ADDQ $32, SP
0x0043 00067 (main1.go:5) RET
同上,main函数恢复BP寄存器,释放之前申请的32字节栈空间
go1.17及之后
go1.17开始,go也采用寄存器传参为主,堆栈辅助的方式,以下是实验环境
go version go1.17.4 windows/amd64
运行go tool compile -N -S -l main.go
之后得到的汇编代码如下,删减了一些无关的代码
// main 函数
0x0006 00006 (main1.go:3) SUBQ $24, SP
0x000a 00010 (main1.go:3) MOVQ BP, 16(SP)
0x000f 00015 (main1.go:3) LEAQ 16(SP), BP
0x0014 00020 (main1.go:4) MOVL $100, AX // 第一个参数放到AX寄存器
0x0019 00025 (main1.go:4) MOVL $200, BX // 第二个参数放到BX寄存器
0x0020 00032 (main1.go:4) CALL "".add(SB)
0x0025 00037 (main1.go:5) MOVQ 16(SP), BP
0x002a 00042 (main1.go:5) ADDQ $24, SP
0x002e 00046 (main1.go:5) RET
// add 函数
0x0000 00000 (main1.go:7) SUBQ $24, SP
0x0004 00004 (main1.go:7) MOVQ BP, 16(SP)
0x0009 00009 (main1.go:7) LEAQ 16(SP), BP
0x000e 00014 (main1.go:7) MOVQ AX, "".a+32(SP) // 读取第一个参是放到sp+32
0x0013 00019 (main1.go:7) MOVQ BX, "".b+40(SP) // 读取第二个参数放到sp+40
0x0018 00024 (main1.go:7) MOVQ $0, "".~r2(SP)
0x0020 00032 (main1.go:8) MOVQ "".a+32(SP), AX
0x0025 00037 (main1.go:8) ADDQ "".b+40(SP), AX // 执行加法运算
0x002a 00042 (main1.go:8) MOVQ AX, "".c+8(SP)
0x002f 00047 (main1.go:9) MOVQ AX, "".~r2(SP)
0x0033 00051 (main1.go:9) MOVQ 16(SP), BP
0x0038 00056 (main1.go:9) ADDQ $24, SP
0x003c 00060 (main1.go:9) RET
在第5,6行,main函数将参数放到了AX,BX寄存器中,然后在第7行调用add函数,下图是add函数返回前的栈,其中sp+32到sp+56是main函数申请的24字节栈空间,而sp到sp+24是add函数申请的24字节栈空间。对于上面的代码有一点疑惑的点是,既然通过寄存器传参,为什么还多此一举将参数放到栈上(第16行和第17行),这是因为这里的汇编代码还没有经过编译器优化(被-N
参数禁止了),如果去掉后就可以发现这两行代码没有了,整个add函数更简洁
寄存器数量是有限的,接下来验证参数数量很多的时候,是如何传参的
参数数量很多的时候
实验代码如下
func main() {
a, b, c, d, e, f, g, h, i, j, k, l, m, n := test(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14)
print(a, b, c, d, e, f, g, h, i, j, k, l, m, n)
}
func test(a, b, c, d, e, f, g, h, i, j, k, l, m, n int) (int, int, int, int, int, int, int, int, int, int, int, int, int, int) {
return a, b, c, d, e, f, g, h, i, j, k, l, m, n
}
代码中test函数共有14个参数,14个返回值,编译出汇编代码如下,删除了一些无关代码
// main函数汇编
// 1. 申请栈空间
0x0012 00018 (main1.go:3) SUBQ $392, SP
0x0019 00025 (main1.go:3) MOVQ BP, 384(SP)
0x0021 00033 (main1.go:3) LEAQ 384(SP), BP
// 2. 传递参数
0x0029 00041 (main1.go:4) MOVQ $10, (SP) // 第10个参数
0x0031 00049 (main1.go:4) MOVQ $11, 8(SP) // 第11个参数
0x003a 00058 (main1.go:4) MOVQ $12, 16(SP) // 第12个参数
0x0043 00067 (main1.go:4) MOVQ $13, 24(SP) // 第13个参数
0x004c 00076 (main1.go:4) MOVQ $14, 32(SP) // 第14个参数
0x0055 00085 (main1.go:4) MOVL $1, AX // 第1个参数
0x005a 00090 (main1.go:4) MOVL $2, BX // 第2个参数
0x005f 00095 (main1.go:4) MOVL $3, CX
0x0064 00100 (main1.go:4) MOVL $4, DI
0x0069 00105 (main1.go:4) MOVL $5, SI
0x006e 00110 (main1.go:4) MOVL $6, R8
0x0074 00116 (main1.go:4) MOVL $7, R9
0x007a 00122 (main1.go:4) MOVL $8, R10
0x0080 00128 (main1.go:4) MOVL $9, R11 // 第9个参数
0x0086 00134 (main1.go:4) CALL "".test(SB) // 调用test函数
// 3. 处理返回值
// 40(SP)到72(SP)存储的是test函数最后5个返回值
0x008b 00139 (main1.go:4) MOVQ 40(SP), DX
0x0090 00144 (main1.go:4) MOVQ 48(SP), R12
0x0095 00149 (main1.go:4) MOVQ 56(SP), R13
0x009a 00154 (main1.go:4) MOVQ 64(SP), R15
0x009f 00159 (main1.go:4) MOVQ R15, ""..autotmp_28+264(SP)
0x00a7 00167 (main1.go:4) MOVQ 72(SP), R15
// 下面的代码从寄存器中获取返回值,放到栈上
0x00ac 00172 (main1.go:4) MOVQ AX, ""..autotmp_14+376(SP)
0x00b4 00180 (main1.go:4) MOVQ BX, ""..autotmp_15+368(SP)
0x00bc 00188 (main1.go:4) MOVQ CX, ""..autotmp_16+360(SP)
0x00c4 00196 (main1.go:4) MOVQ DI, ""..autotmp_17+352(SP)
0x00cc 00204 (main1.go:4) MOVQ SI, ""..autotmp_18+344(SP)
0x00d4 00212 (main1.go:4) MOVQ R8, ""..autotmp_19+336(SP)
0x00dc 00220 (main1.go:4) MOVQ R9, ""..autotmp_20+328(SP)
0x00e4 00228 (main1.go:4) MOVQ R10, ""..autotmp_21+320(SP)
0x00ec 00236 (main1.go:4) MOVQ R11, ""..autotmp_22+312(SP)
0x00f4 00244 (main1.go:4) MOVQ DX, ""..autotmp_23+304(SP)
0x00fc 00252 (main1.go:4) MOVQ R12, ""..autotmp_24+296(SP)
0x0104 00260 (main1.go:4) MOVQ R13, ""..autotmp_25+288(SP)
0x010c 00268 (main1.go:4) MOVQ ""..autotmp_28+264(SP), DX
0x0114 00276 (main1.go:4) MOVQ DX, ""..autotmp_26+280(SP)
0x011c 00284 (main1.go:4) MOVQ R15, ""..autotmp_27+272(SP)
// 下面将参数放到对应的变量位置上
0x0124 00292 (main1.go:4) MOVQ ""..autotmp_14+376(SP), DX
0x012c 00300 (main1.go:4) MOVQ DX, "".a+256(SP)
0x0134 00308 (main1.go:4) MOVQ ""..autotmp_15+368(SP), DX
0x013c 00316 (main1.go:4) MOVQ DX, "".b+248(SP)
0x0144 00324 (main1.go:4) MOVQ ""..autotmp_16+360(SP), DX
0x014c 00332 (main1.go:4) MOVQ DX, "".c+240(SP)
0x0154 00340 (main1.go:4) MOVQ ""..autotmp_17+352(SP), DX
0x015c 00348 (main1.go:4) MOVQ DX, "".d+232(SP)
0x0164 00356 (main1.go:4) MOVQ ""..autotmp_18+344(SP), DX
0x016c 00364 (main1.go:4) MOVQ DX, "".e+224(SP)
0x0174 00372 (main1.go:4) MOVQ ""..autotmp_19+336(SP), DX
0x017c 00380 (main1.go:4) MOVQ DX, "".f+216(SP)
0x0184 00388 (main1.go:4) MOVQ ""..autotmp_20+328(SP), DX
0x018c 00396 (main1.go:4) MOVQ DX, "".g+208(SP)
0x0194 00404 (main1.go:4) MOVQ ""..autotmp_21+320(SP), DX
0x019c 00412 (main1.go:4) MOVQ DX, "".h+200(SP)
0x01a4 00420 (main1.go:4) MOVQ ""..autotmp_22+312(SP), DX
0x01ac 00428 (main1.go:4) MOVQ DX, "".i+192(SP)
0x01b4 00436 (main1.go:4) MOVQ ""..autotmp_23+304(SP), DX
0x01bc 00444 (main1.go:4) MOVQ DX, "".j+184(SP)
0x01c4 00452 (main1.go:4) MOVQ ""..autotmp_24+296(SP), DX
0x01cc 00460 (main1.go:4) MOVQ DX, "".k+176(SP)
0x01d4 00468 (main1.go:4) MOVQ ""..autotmp_25+288(SP), DX
0x01dc 00476 (main1.go:4) MOVQ DX, "".l+168(SP)
0x01e4 00484 (main1.go:4) MOVQ ""..autotmp_26+280(SP), DX
0x01ec 00492 (main1.go:4) MOVQ DX, "".m+160(SP)
0x01f4 00500 (main1.go:4) MOVQ ""..autotmp_27+272(SP), DX
0x01fc 00508 (main1.go:4) MOVQ DX, "".n+152(SP)
... // 这里是调用print的代码,省略
0x02cb 00715 (main1.go:6) MOVQ 384(SP), BP
0x02d3 00723 (main1.go:6) ADDQ $392, SP
0x02da 00730 (main1.go:6) RET
// test 函数
// 1. 申请栈空间
0x0000 00000 (main1.go:8) SUBQ $80, SP
0x0004 00004 (main1.go:8) MOVQ BP, 72(SP)
0x0009 00009 (main1.go:8) LEAQ 72(SP), BP
// 2. 获取参数放到栈上
0x000e 00014 (main1.go:8) MOVQ AX, "".a+168(SP)
0x0016 00022 (main1.go:8) MOVQ BX, "".b+176(SP)
0x001e 00030 (main1.go:8) MOVQ CX, "".c+184(SP)
0x0026 00038 (main1.go:8) MOVQ DI, "".d+192(SP)
0x002e 00046 (main1.go:8) MOVQ SI, "".e+200(SP)
0x0036 00054 (main1.go:8) MOVQ R8, "".f+208(SP)
0x003e 00062 (main1.go:8) MOVQ R9, "".g+216(SP)
0x0046 00070 (main1.go:8) MOVQ R10, "".h+224(SP)
0x004e 00078 (main1.go:8) MOVQ R11, "".i+232(SP)
// 3. 这里是初始化返回值,总共14个返回值,在栈空间上
0x0056 00086 (main1.go:8) MOVQ $0, "".~r14+64(SP)
0x005f 00095 (main1.go:8) MOVQ $0, "".~r15+56(SP)
0x0068 00104 (main1.go:8) MOVQ $0, "".~r16+48(SP)
0x0071 00113 (main1.go:8) MOVQ $0, "".~r17+40(SP)
0x007a 00122 (main1.go:8) MOVQ $0, "".~r18+32(SP)
0x0083 00131 (main1.go:8) MOVQ $0, "".~r19+24(SP)
0x008c 00140 (main1.go:8) MOVQ $0, "".~r20+16(SP)
0x0095 00149 (main1.go:8) MOVQ $0, "".~r21+8(SP)
0x009e 00158 (main1.go:8) MOVQ $0, "".~r22(SP)
0x00a6 00166 (main1.go:8) MOVQ $0, "".~r23+128(SP)
0x00b2 00178 (main1.go:8) MOVQ $0, "".~r24+136(SP)
0x00be 00190 (main1.go:8) MOVQ $0, "".~r25+144(SP)
0x00ca 00202 (main1.go:8) MOVQ $0, "".~r26+152(SP)
0x00d6 00214 (main1.go:8) MOVQ $0, "".~r27+160(SP)
// 4. 将返回值放在对应的位置上
0x00e2 00226 (main1.go:9) MOVQ "".a+168(SP), DX
0x00ea 00234 (main1.go:9) MOVQ DX, "".~r14+64(SP)
0x00ef 00239 (main1.go:9) MOVQ "".b+176(SP), DX
0x00f7 00247 (main1.go:9) MOVQ DX, "".~r15+56(SP)
0x00fc 00252 (main1.go:9) MOVQ "".c+184(SP), DX
0x0104 00260 (main1.go:9) MOVQ DX, "".~r16+48(SP)
0x0109 00265 (main1.go:9) MOVQ "".d+192(SP), DX
0x0111 00273 (main1.go:9) MOVQ DX, "".~r17+40(SP)
0x0116 00278 (main1.go:9) MOVQ "".e+200(SP), DX
0x011e 00286 (main1.go:9) MOVQ DX, "".~r18+32(SP)
0x0123 00291 (main1.go:9) MOVQ "".f+208(SP), DX
0x012b 00299 (main1.go:9) MOVQ DX, "".~r19+24(SP)
0x0130 00304 (main1.go:9) MOVQ "".g+216(SP), DX
0x0138 00312 (main1.go:9) MOVQ DX, "".~r20+16(SP)
0x013d 00317 (main1.go:9) MOVQ "".h+224(SP), DX
0x0145 00325 (main1.go:9) MOVQ DX, "".~r21+8(SP)
0x014a 00330 (main1.go:9) MOVQ "".i+232(SP), DX
0x0152 00338 (main1.go:9) MOVQ DX, "".~r22(SP)
0x0156 00342 (main1.go:9) MOVQ "".j+88(SP), DX
0x015b 00347 (main1.go:9) MOVQ DX, "".~r23+128(SP)
0x0163 00355 (main1.go:9) MOVQ "".k+96(SP), DX
0x0168 00360 (main1.go:9) MOVQ DX, "".~r24+136(SP)
0x0170 00368 (main1.go:9) MOVQ "".l+104(SP), DX
0x0175 00373 (main1.go:9) MOVQ DX, "".~r25+144(SP)
0x017d 00381 (main1.go:9) MOVQ "".m+112(SP), DX
0x0182 00386 (main1.go:9) MOVQ DX, "".~r26+152(SP)
0x018a 00394 (main1.go:9) MOVQ "".n+120(SP), DX
0x018f 00399 (main1.go:9) MOVQ DX, "".~r27+160(SP)
// 这里可以看到,将前9个返回值放到了AX...R11寄存器上
0x0197 00407 (main1.go:9) MOVQ "".~r14+64(SP), AX
0x019c 00412 (main1.go:9) MOVQ "".~r15+56(SP), BX
0x01a1 00417 (main1.go:9) MOVQ "".~r16+48(SP), CX
0x01a6 00422 (main1.go:9) MOVQ "".~r17+40(SP), DI
0x01ab 00427 (main1.go:9) MOVQ "".~r18+32(SP), SI
0x01b0 00432 (main1.go:9) MOVQ "".~r19+24(SP), R8
0x01b5 00437 (main1.go:9) MOVQ "".~r20+16(SP), R9
0x01ba 00442 (main1.go:9) MOVQ "".~r21+8(SP), R10
0x01bf 00447 (main1.go:9) MOVQ "".~r22(SP), R11
0x01c3 00451 (main1.go:9) MOVQ 72(SP), BP
0x01c8 00456 (main1.go:9) ADDQ $80, SP
0x01cc 00460 (main1.go:9) RET
从上面的汇编代码可以看到,前9个参数依次放在了AX,BX,CX,DI,SI,R8,R9,R10,R11
寄存器上,如果还有更多的参数,则会放在栈上。返回结果的时候也是如此
其他情况
既然寄存器个数为9,那如果参数为结构体,且大小不能被9整除的时候,会不会把参数分开呢,比如一个结构体大小为32个字节的时候
实验代码如下,这里我们定义了一个A结构体,大小为32字节,正好能被4个寄存器存放。同时定义了test函数,其参数为3个A结构体,看看会不会出现最后一个参数一部分在栈上,一部分在寄存器上
package main
func main() {
test(A{10, 10, 10, 10}, A{20, 20, 20, 20}, A{30, 30, 30, 30})
}
func test(a, b, c A) (A, A, A) {
return a, b, c
}
type A struct {
a int
b int
c int
d int
}
编译成汇编代码后,得到如下输出,这里就不展示全部的汇编代码了,只展示传参时候的代码,可以看到这里前两个参数都放在了寄存器上,最后一个参数放在了栈上
0x00ec 00236 (main1.go:4) MOVQ ""..autotmp_0+192(SP), AX
0x00f4 00244 (main1.go:4) MOVQ ""..autotmp_1+160(SP), SI
0x00fc 00252 (main1.go:4) MOVQ ""..autotmp_0+200(SP), BX
0x0104 00260 (main1.go:4) MOVQ ""..autotmp_1+168(SP), R8
0x010c 00268 (main1.go:4) MOVQ ""..autotmp_0+208(SP), CX
0x0114 00276 (main1.go:4) MOVQ ""..autotmp_1+176(SP), R9
0x011c 00284 (main1.go:4) MOVQ ""..autotmp_0+216(SP), DI
0x0124 00292 (main1.go:4) MOVQ ""..autotmp_1+184(SP), R10
0x012c 00300 (main1.go:4) MOVQ $30, (SP)
0x0134 00308 (main1.go:4) MOVQ $30, 8(SP)
0x013d 00317 (main1.go:4) MOVQ $30, 16(SP)
0x0146 00326 (main1.go:4) MOVQ $30, 24(SP)