通过分析汇编得到的go1.17前后函数传参详情
在go1.17的release note中,有这样一句话:Go 1.17 implements a new way of passing function arguments and results using registers instead of the stack. 也就是说,在go1.17之前,函数传参的方式是通过栈来传递的,而在go1.17之后,函数传参的方式变成了通过寄存器来传递,这样做的好处是可以减少栈的使用,提高函数调用的效率。在这里我通过分析汇编,得到了更多go语言函数调用的细节,这里做一个简单的记录。
demo
在下面的例子中,我们定义了一个函数Func
,它接受12个参数,返回11个返回值。然后我们在main
函数中调用了Func
函数。
1 | package main |
下面我们都用这个例子来说明。
1. go1.17之前的函数传参方式
在go1.17之前,函数不论是参数还是返回值,都是用栈。在调用函数时,首先在栈上为返回值留出空间,然后将参数依次压入栈中,最后调用函数(同时压入返回地址)。不论是参数还是返回值都是按照从右到左的顺序压入栈中。
对于上面的函数,可以得到如下的效果:
2. go1.17之后的函数传参方式
在go1.17及之后,会将前九个参数和返回值放在寄存器里面,其余的参数和返回值放在栈里面。寄存器的使用顺序是rax, rbx, rcx, rdi, rsi, r8d, r9d, r10d, r11d
,栈上的参数和返回值的顺序是从右到左的依次入栈的。
对于上面的函数,可以得到如下的效果:
go语言中的复杂类型是如何传递的
我还研究了一下go语言中的复杂类型是如何传递的,比如struct、slice等。
struct
对于struct类型,实际上是把struct做展开,对于struct中的每个元素依次通过寄存器或者栈传递。比如下面的例子,对于go1.17之前的一个结构体参数,在栈上有这样的效果:
slice
对于slice类型,实际上需要同时传递三个参数:指向slice的指针、slice的长度和slice的容量。对于go1.17之前的一个slice参数,在栈上有这样的效果:
go1.17之后有相似的效果,只是可能会放在寄存器里面。
方法
对于方法,实际上是把方法的接收者作为第一个参数传递,然后再传递其他参数。但是我通过分析汇编发现,如果方法的接收者在方法中并没有真正用到的话,那么在调用方法时,会直接跳过接收者,直接传递其他参数。比如下面的例子,汇编代码中可能就会忽略demo指针的传递:
1 | type demo struct { |