golang函数调用栈

文章目录

  • golang函数调用栈
    • 函数栈帧
    • 函数跳转与返回

如果在一个函数中调用另一个函数,编译器就会对应生成一条call指令,程序执行到这条指令时,就会跳转到被调用函数入口处开始执行,而每个函数的最后都有一条ret指令,负责在函数结束后跳回到调用处,继续执行。

函数栈帧

函数执行时需要有足够的内存空间,供它存放局部变量、参数等数据,这段空间对应到虚拟地址空间的
运行时栈,上面是高地址,向下增长,栈底通常被称为,栈顶被称为栈指针

分配给函数的栈空间被称为函数栈帧
Go语言中函数栈帧布局是这样的,先是调用者栈基地址,然后是函数的局部变量,最后是被调用函数的返回值和参数。


BP of calleeSP of callee标识被调用函数执行时,栈基寄存器和栈指针寄存器指向的位置,但是注意“BP of caller”不一定会存在,有些情况下可能会被优化掉,也有可能是平台不支持。我们只关注局部变量和参数、返回值的相对位置就好。

下面举一个例子:

func A() {var a1, a2, r1, r2 int64a1, a2 = 1, 2r1, r2 = B(a1, a2)r1 = C(a1)println(r1, r2)
}
func B(p1, p2 int64) (int64, int64) {return p2, p1
}
func C(p1 int64) int64 {return p1
}

函数A的栈帧布局如下图所示:

注意观察参数的顺序,先入栈第二个参数,再入栈第一个参数,返回值也是一样的,上面是第二个返回值的空间,然后才是第一个返回值的空间。
因为这些是被调用函数的返回值和参数,被调用函数是通过栈指针加上偏移值这样相对寻址的方式来定位到自己的参数和返回值的,这样由下至上正好先找到第一个参数,再找到第二个参数。所以参数和返回值采用由右至左的入栈顺序比较合适。
通常,我们认为返回值是通过寄存器传递的,但是Go语言支持多返回值,所以在栈上分配返回值空间更合适。

对函数B的调用会被编译器编译为call指令。实际上call指令只做两件事情:

  • 将下一条指令的地址入栈,被调用函数执行结束后会跳回到这个地址继续执行,这就是函数调用的“返回地址”。
  • 跳转到被调用的函数B指令入口处执行,所以在“返回地址”下面就是函数B的栈帧了。

所有函数的栈帧布局都遵循统一的约定,函数B结束后它的栈帧被释放,回到函数A中继续执行。

到了调用函数C的时候,它只有一个参数和一个返回值,它们会占用函数A栈帧中最下面的一部分空间,所以上面会空出来一块,这是为了在被调用函数中可以用标准的相对地址定位到自己的参数和返回值,而无需顾虑其它。

同样的,call指令会压入返回地址,并跳转到函数C的指令入口处,所以下面就是函数C的栈帧了。

Go语言中,函数栈帧是一次性分配的,也就是在函数开始执行的时候分配足够大的栈帧空间。就像上例中函数A一样,它要调用两个函数,除了调用者栈基地址、局部变量以外,再有四个int64的空间用作被调用函数的参数与返回值就足够了。
一次性分配函数栈帧的主要原因是避免栈访问越界,如下图所示,三个goroutine初始分配的栈空间是一样的,如果g2剩余的栈空间不够执行接下来的函数,若函数栈帧是逐步扩张的,那么执行期间就可能发生栈访问越界。

其实,对于栈消耗较大的函数,go语言的编译器还会在函数头部插入检测代码,如果发现需要进行“栈增长”,就会另外分配一段足够大的栈空间,并把原来栈上的数据拷过来,原来的这段栈空间就被释放了。

函数跳转与返回

程序执行时 CPU用特定寄存器来存储运行时栈基与栈指针,同时也有指令指针寄存器用于存储下一条要执行的指令地址。

如果接下来要执行"push 3"这条指令,CPU读取后,会将指令指针移向下一条指令,然后栈指针向下移动,数字3入栈。

继续执行下一条指令,再次移动栈指针入栈数字4。

前面我们提过Go语言中函数栈帧不是这样逐步扩张的,而是一次性分配,也就是在分配栈帧时,直接将栈指针移动到所需最大栈空间的位置。

然后通过栈指针加上偏移值这种相对寻址方式使用函数栈帧。例如sp加16字节处存储3,加8字节处存储4,诸如此类。

接下来我们来看看call指令和ret指令,是怎样实现函数跳转与返回的。

func A(){a,b := 1,2B(a,b)return
}
func B(c,d int){println(c,d)return
}

调用函数B之前函数A栈帧如下图所示,注意函数A和函数B的指令分布在代码段,而且函数A调用函数B的call指令在地址a1处,函数B入口地址在b1处。

然后到call指令这里,它的作用有两点:

  • 第一,把返回地址a2入栈保存起来;
  • 第二,跳转到指令地址b1处。

call指令结束。函数B开始执行,我们先看它最开始的三条指令:

  • 第一条指令,把SP向下移动24字节(从s6挪到s9),为自己分配足够大的栈帧;
  • 第二条指令,要把调用者栈基s1存到SP+16字节的地方(s7那里);
  • 第三条指令,把s7(SP+16)存入BP寄存器。

接下来就是执行函数B剩下的指令了,没有局部变量,只有被调用者的参数空间。在最后的ret指令之前,编译器还会插入两条指令:

  • 第1条指令:恢复调用者A的栈基地址,它之前被存储在SP+16字节(s7)这里,所以BP恢复到s1;
  • 第2条指令:释放自己的栈帧空间,分配时向下移动多少(从s6到s9)释放时就向上移动多少(从s9到s6)。

现在可以从a2这里继续执行了。简单来说,函数通过call指令实现跳转,而每个函数开始时会分配栈帧,结束前又释放自己的栈帧,ret指令又会把栈恢复到call之前的样子,通过这些指令的配合最终实现了函数跳转与返回。

参考资料:

https://mp.weixin.qq.com/s/zcqzarXMJrDUY5DLXZXY1Q

golang 函数调用栈相关推荐

  1. swap函数_【Golang】图解函数调用栈

    " 不要小瞧函数调用栈哦,它可是理解参数传递.命名匿名返回值.Function Value.defer等面试常客的关键呐~" 我们按照编程语言的语法定义的函数,会被编译器编译为一堆 ...

  2. 关于ceph源码 backtrace 打印函数调用栈

    当集中精力看一个问题的时候,时间久了就会有这样一个状态,天空飘来五个字,那都不算事 ceph源码庞大的体量以及复杂的设计让很多人望而却步,尤其是大量的纯虚函数更是让读者迷失在代码的海洋,这个时候函数调 ...

  3. 如何在系统崩溃时从C++中获取函数调用栈信息?

    这篇文章主要讲述在 Linux 和 Windows 这 2 个平台上,如何用C++ 来捕获函数调用栈里的信息. 一.前言 程序在执行过程中 crash 是非常严重的问题,一般都应该在测试阶段排除掉这些 ...

  4. 一、 函数调用栈,执行上下文及变量对象

    前言 为什么会有这篇文章? 在书籍或博客上,我们经常会看到「作用域链」.「闭包」.「变量提升」等概念,说明一个问题 -- 它们很重要. 但很多时候,对于这些概念,看的时候觉得自己已经明白了,可过不了多 ...

  5. 函数调用栈的获取原理分析【转】

    转自:http://hutaow.com/blog/2013/10/15/dump-stack/ 上一篇文章<在Linux程序中输出函数调用栈>,讲述了在Linux中如何利用backtra ...

  6. python查看函数调用栈

    我在看开源框架代码的时候,有时候好几天都无法找到一个函数被调用的具体位置..这个时候就需要记录一下函数调用栈. 源码如下 def Caller(func):def f(*args,**kwargs): ...

  7. 在Linux程序中输出函数调用栈

    程序发生异常时,将函数的调用栈打印出来,可以大大提高定位效率. Linux中提供了三个函数用来获取调用栈: /* 获取函数调用栈 */ int backtrace(void **buffer, int ...

  8. 函数调用栈 剖析+图解

    栈: 在函数调用时,第一个进栈的是主函数中函数调用后的下一条指令(函数调用语句的下一条可执行语句)的地址,然后是函数的各个参数,在大多数的C编译器中,参数是由右往左入栈的,然后是函数中的局部变量.注意 ...

  9. 【软件开发底层知识修炼】十七 快速学习GDB调试四 使用GDB进行函数调用栈的查看

    上一篇文章学习了如何使用GDB数据断点进行内存监测:[软件开发底层知识修炼]十五 快速学习GDB调试三 使用GDB的数据断点监测变量是否改变 本篇文章继续上一篇文章的学习:如何使用GDB进行函数调用栈 ...

最新文章

  1. 南大计算机学硕复试,2019南大CS考研复试笔试回忆
  2. 初识 Knative: 跨平台的 Serverless 编排框架
  3. 3D打印机分类与速度
  4. 百果园付凌峰:线上单月 1.2 亿背后的数据化运营
  5. Linux双网卡NAT共享上网
  6. [No0000D0] 让你效率“猛增十倍”,沉浸工作法到底是什么?
  7. Mysql数据库大表归档操作
  8. 高级语言中的关键字:const用法分析
  9. android gps 案例_GPS学习要点10
  10. Java面试题详解一:面向对象三大特性
  11. centos 5开机出现PCI错误:Not using MMCONFIG
  12. Android Fatal signal 11 (SIGSEGV), code 1 (SEGV_MAPERR), fault addr 0x10 in tid 5287 (HeapTaskDaemo
  13. 编译好的编译ffmpeg又出错:更改输出目录产生各种古怪错误
  14. PowerDesigner 数据字典模板
  15. 如何看硬盘SMART参数----用HDtune工具查看
  16. Java实验9 矩形类的定义与封装
  17. 怎么讲计算机e盘设置共享,共享盘怎么设置(电脑如何设置共享盘)
  18. 【STM32项目】老人健康跌倒检测系统实现
  19. 【my eclipse tips】Could not create the view: An unexpected exception was thrown问题
  20. 三行CSS代码搞定镜头平移(Panning Shot)动画

热门文章

  1. (开源)微信小程序控制esp8266
  2. Mysql军规(编写规范)
  3. A First-Person Camera
  4. 计算机辅助设计与制造考试重点,华中科技大学2017博士招生:计算机辅助设计与制造考试大纲...
  5. 2020年计算机视觉市场现状与竞争格局分析,集成化、小型化的产品将会成为主要发展方向之一「图」
  6. MYSQL中导入Excel文件
  7. Android代码加固技术
  8. VC++ 工程添加 Unicode Debug 和 Unicode Release编译支持
  9. windows免费版切割pdf拆分pdf提取pdf指定页码小工具
  10. 子字符串组合 java_abc三字符实现排列组合-JAVA版