查看生成的汇编代码
go tool compile -S xx.go //编译器对汇编代码进行优化
go tool compile -S -N -l xx.go //不优化
注:如果编译时不使用 -N -l 参数,编译器会对汇编代码进行优化,编译结果会有较大差别
c语言汇编分析
// ch04/my_function.c
int my_function(int arg1, int arg2) {
return arg1 + arg2;
}
int main() {
int i = my_function(1, 2);
}
cc -S my_function.c
当我们在 x86_64 的机器上使用 C 语言中调用函数时,参数都是通过寄存器和栈传递的,其中:
六个以及六个以下的参数会按照顺序分别使用 edi、esi、edx、ecx、r8d 和 r9d 六个寄存器传递;
六个以上的参数会使用栈传递,函数的参数会以从右到左的顺序依次存入栈中;
而函数的返回值是通过 eax 寄存器进行传递的,由于只使用一个寄存器存储返回值,所以 C 语言的函数不能同时返回多个值。
go语言汇编分析
//main.go
package main
func myFunction(a, b int) (int, int) {
return a + b, a - b
}
func main() {
myFunction(66, 77)
}
go tool compile -S -N -l main.go
Go 语言使用栈传递参数和接收返回值,所以它只需要在栈上多分配一些内存就可以返回多个值。
总结:
C 语言和 Go 语言在设计函数的调用惯例时选择了不同的实现。C 语言同时使用寄存器和栈传递参数,使用 eax 寄存器传递返回值;而 Go 语言使用栈传递参数和返回值。我们可以对比一下这两种设计的优点和缺点:
C 语言的方式能够极大地减少函数调用的额外开销,但是也增加了实现的复杂度;
CPU 访问栈的开销比访问寄存器高几十倍3;
需要单独处理函数参数过多的情况;
Go 语言的方式能够降低实现的复杂度并支持多返回值,但是牺牲了函数调用的性能;
不需要考虑超过寄存器数量的参数应该如何传递;
不需要考虑不同架构上的寄存器差异;
函数入参和出参的内存空间需要在栈上进行分配;
Go 语言使用栈作为参数和返回值传递的方法是综合考虑后的设计,选择这种设计意味着编译器会更加简单、更容易维护。
Go 语言函数调用惯例:
Go 通过栈传递函数的参数和返回值,在调用函数之前会在栈上为返回值分配合适的内存空间,随后将入参从右到左按顺序压栈并拷贝参数,返回值会被存储到调用方预留好的栈空间上,我们可以简单总结出以下几条规则:
通过堆栈传递参数,入栈的顺序是从右到左;
函数返回值通过堆栈传递并由调用者预先分配内存空间;
调用函数时都是传值,接收方会对入参进行复制再计算;
附图:go汇编的分析过程
引用:
https://draveness.me/golang/docs/part2-foundation/ch04-basic/golang-function-call/
https://chai2010.cn/advanced-go-programming-book/ch3-asm/ch3-04-func.html