函数栈 #
了解 go 的函数调用底层逻辑,能更清晰的理解 defer、recover、panic 的工作方式。go 的函数栈如下:

栈从高地址往低地址依次是:
- 栈基
- 局部标量
- 调用函数返回值
- 调用函数参数
- 函数返回地址
- 栈指针
func main() {
  int a = 5;
  println(a);
}在为 main 函数分配函数栈时,会将使用两个 int 大小的空间 r1、r2 分别存储变量 a 和传入 println 函数的参数 a。你可能会好奇为什么 a 需要占两份,因为对于 go 来说,函数参数皆是值拷贝,只是区别是拷贝目标对象还是拷贝指针,函数调用前就会一次性为形参和返回值分配内存空间,并将实参拷贝到形参中。由于 println 没有返回值,所以仅有两个变量 a 的空间。
指令 #
理解了以上后,就需要知道一些额外的指令
call 指令 #
- 将下一条指令入栈,作为返回地址
- 跳转到被调用者函数执行
执行函数前的处理:
- 入栈调用函数栈的栈基
- 分配函数栈
- 设置被调用函数的栈基(bp)
执行函数 给返回值赋值 执行 defer 函数
执行 ret 指令前的处理:
- 恢复调用函数的栈基
- 释放被调函数的函数栈
ret 指令 #
- 弹出调用前入栈的返回地址
- 跳转到该返回地址
Base Stack #
传值/传指针 #
传值
func swap(a, b int) {
  a, b = b, a
}
func main() {
  a, b = 1, 2
  swap(a, b)
  fmt.Println(a, b) // 1, 2
}分析以上代码,main 方法在执行时会给局部变量 a、b,调用 swap 的两个参数(参数从右往左)分配栈空间并赋值如下:
| 栈帧 | |
|---|---|
| … | |
| 局部变量 a = 1 | main BP | 
| 局部变量 b = 2 | |
| swap 参数 b = 2 | |
| swap 参数 a = 1 | |
| 返回地址 | main SP | 
| main BP | |
| … | 
swap 方法中会将参数 a,b 交换:
| 栈帧 | |
|---|---|
| … | |
| 局部变量 a = 1 | main BP | 
| 局部变量 b = 2 | |
| swap 参数 b = 1 | |
| swap 参数 a = 2 | |
| 返回地址 | main SP | 
| main BP | |
| … | 
可以看到,swap 方法并没有修改调用者的变量,因此 main 方法中的 a,b 交换失败了。
传指针
func swap(a, b *int) {
  a, b = b, a
}
func main() {
  a, b = 1, 2
  swap(&a, &b)
  fmt.Println(a, b) // 1, 2
}| 栈帧 | ||
|---|---|---|
| … | ||
| addrA | a = 1 | main BP | 
| addrB | b = 2 | |
| swap 参数 b = addrB | ||
| swap 参数 a = addrA | ||
| 返回地址 | main SP | |
| main BP | ||
| … | 
可以看到,此时 swap 方法交换的是 addrA 和 addrB 对应的数据,此时就可以交换成功了。
Function Receiver #
type A struct {
}
func (A) F1(string) string {
  return ""
}
func (*A) F2(string) string {
  return ""
}
func f1(A, string) string {
  return "xxx"
}
func f2(*A, string) string {
  return "xxx"
}
func main() {
  fmt.Println(reflect.TypeOf(A.F1) == reflect.TypeOf(f1))    // true
  fmt.Println(reflect.TypeOf((*A).F2) == reflect.TypeOf(f2)) // true
}返回值 #
匿名返回值
func inc(a int) int {
  var b int
  defer func() {
    a++
    b++
  }()
  a++
  b = a
  return b
}
func main() {
  var a, b int
  b = inc(a)
  fmt.Println(a, b) // 0, 1
}before call inc
| 栈帧 | |
|---|---|
| … | |
| 局部变量 a = 0 | main BP | 
| 局部变量 b = 0 | |
| inc返回值 0 | |
| inc参数 a = 0 | |
| 返回地址 | main SP | 
| main BP | |
| 局部变量 b = 0 | inc BP | 
进入 inc 方法后,会将 a 增加 1 并赋值给 b,最后将 b 赋值给返回值
before return
| 栈帧 | |
|---|---|
| … | |
| 局部变量 a = 0 | main BP | 
| 局部变量 b = 0 | |
| inc返回值 1 | |
| inc参数 a = 1 | |
| 返回地址 | main SP | 
| main BP | |
| 局部变量 b = 1 | inc BP | 
在返回 b 之后会执行 defer 匿名函数的内容,将 a,b 都增加 1,最后返回:
handle defer
| 栈帧 | |
|---|---|
| … | |
| 局部变量 a = 0 | main BP | 
| 局部变量 b = 0 | |
| inc返回值 = 1 | |
| inc参数 a = 2 | |
| 返回地址 | main SP | 
| main BP | |
| 局部变量 b = 2 | inc BP | 
返回 main 方法后,将返回值赋值给局部变量 b:
return main
| 栈帧 | |
|---|---|
| … | |
| 局部变量 a = 0 | main BP | 
| 局部变量 b = 1 | |
| inc返回值 1 | |
| inc参数 a = 2 | |
| 返回地址 | main SP | 
| main BP | |
| … | 
所以最后输出的 a,b 为 0,1。
命名返回值
func inc(a int) (b int) {
  defer func() {
    a++
    b++
  }()
  a++
  b = a
  return b
}
func main() {
  var a, b int
  b = inc(a)
  fmt.Println(a, b) // 0, 2
}Details
| 栈帧 | |
|---|---|
| … | |
| 局部变量 a = 0 | main BP | 
| 局部变量 b = 0 | |
| inc返回值 0 | |
| inc参数 a = 0 | |
| 返回地址 | main SP | 
| main BP | 
Details
| 栈帧 | |
|---|---|
| … | |
| 局部变量 a = 0 | main BP | 
| 局部变量 b = 0 | |
| inc返回值 1 | |
| inc参数 a = 1 | |
| 返回地址 | main SP | 
| main BP | 
Details
| 栈帧 | |
|---|---|
| … | |
| 局部变量 a = 0 | main BP | 
| 局部变量 b = 0 | |
| inc返回值 2 | |
| inc参数 a = 2 | |
| 返回地址 | main SP | 
| main BP | 
Details
| 栈帧 | |
|---|---|
| … | |
| 局部变量 a = 0 | main BP | 
| 局部变量 b = 2 | |
| inc返回值 2 | |
| inc参数 a = 2 | |
| 返回地址 | main SP | 
| main BP | 
基于寄存器 #
- Proposal: Register-based Go calling convention
- https://www.kandaoni.com/news/56576.html
- https://www.kuangstudy.com/m/bbs/1624703556664086530
- https://www.cnblogs.com/luozhiyun/p/14844710.html
- https://www.kandaoni.com/news/56576.html
- https://www.bilibili.com/video/BV1WZ4y1p7JT/?spm_id_from=333.337.search-card.all.click&vd_source=ddc8289a36a2bf501f48ca984dc0b3c1
- https://www.bilibili.com/video/BV1tZ4y1p7Rv/?spm_id_from=333.788.recommend_more_video.-1&vd_source=ddc8289a36a2bf501f48ca984dc0b3c1