Golang的学习(2)——函数调用约定
函数调用约定(Calling Convention)定义了函数调用时参数的传递方式、堆栈的清理责任以及寄存器的使用规则。
C/C++函数调用约定
顺便先来复习一下C/C++的函数调用约定
_cdecl (C declaration)
C的默认调用方式,通过栈传递参数,参数从右往左依次入栈,由caller清理栈
_stdcall(StandardCall)
又叫做Pascal调用,C++的标准调用,和前者主要差别就是他是由callee来清理栈
fastcall
快速调用,用寄存器传递参数(前两个参数通过ecx和edx传入,后续参数依旧从右至入栈,由callee清理栈。
windows x64下默认为用四个寄存器,整数参数在寄存器 RCX、RDX、R8 和 R9 中传递。 浮点数参数在 XMM0L、XMM1L、XMM2L 和 XMM3L 中传递。由caller来清栈;Linux/Unix 64位则用6个寄存器(RDI, RSI, RDX, RCX, R8, R9),同样由caller清栈
Golang函数调用约定
下面主要是说的Go1.17版本之后的
参数传递
参数的传递上Golang的调用也是基于快速调用的原理,都是先通过寄存器传参,不够了再通过栈传递。不同的是寄存器的数量使用,函数的第 1~9 个参数依次使用 AX、BX、CX、DI、SI、R8、R9、R10、R11 寄存器。从第 10 个参数开始,使用栈传递,并且是从栈顶向栈低依次排列。执行顺序上先入栈,然后再对寄存器赋值。
下面来看一下同样一段实现加法的函数,go和c/c++在函数调用栈上有什么差别
1 | // go |
懒得画图了,直接看他们的反汇编实现(这里go的反汇编我是通过dlv源代码级调试的,直接编译成二进制文件的话,go的编译器有函数内联和常数传播机制,会把我的这段代码直接优化掉,后面再来详细学习了解一下)

这里c我就直接ida里看了

可以看到同样是12个参数,go采用了九个寄存器,之后的参数通过压栈传递,并且他是通过定位sp的偏移来寻址变量,这一点也与c通过bp寻址也有所不同
栈空间管理
c/c++的函数栈是固定大小的,所以会存在栈溢出
go的栈空间是动态可扩的,主要是在函数入口前插入一段栈检查代码,调用runtime中的morestack函数来检查当前的栈帧是否超出了分配给 Goroutine 的栈空间,如果栈空间足够,runtime.morestack_noctxt会立即返回,执行流继续往下走,反之则会进行栈扩容操作,分配一个更大的栈,并将当前栈上的数据复制过去,然后返回。

下面是morestack中栈扩容的核心newstack函数
1 | func newstack() { |
多返回值
go语言是支持多返回值的,主要原因是他将返回值保存在参数列表,然后和参数同样的待遇,优先通过寄存器传递值,不够了便通过压栈传递

- 标题: Golang的学习(2)——函数调用约定
- 作者: w1n9
- 创建于 : 2025-07-25 16:05:56
- 更新于 : 2026-01-14 19:48:44
- 链接: https://vv1n9.github.io/2025/07/25/Golang八股学习(2)——函数调用约定/
- 版权声明: 本文章采用 CC BY-NC-SA 4.0 进行许可。