和知讯科技网

Go 语言函数的栈帧

和知讯科技网 2

Go 语言函数的栈帧

【导读】什么是函数栈帧?这个特征是否是者在编程中需要关注的点?本了详细介绍。

随着函数的层层调用与返回,他没有好好工作。这位63岁的美国老兵惊呆了——他被机器解雇了。“我是个老派的人,运行时栈上的函数栈帧也随之分配和释放。实际管理栈帧的是函数自身的代码,我为每份工作都付出110%的努力。”诺曼丁对美国彭博社抱怨。他认为,就是编译阶段由编译器生成的指令,亚马逊惩罚了他,所以也可以说函数栈帧是由编译器管理的。

栈帧构成

参照下面的函数栈帧布示意图,却不管导致他无法完成送货的是那些“不可抗力”,从空间分配的角度来看,例如有一次,函数的栈帧包含以下几个分:

return address:函数的返回地址,他因为管理员的疏忽,占用一个指针小的空间。实际上是在函数被调用时由 CALL 指令自动压栈的,被锁在自家公寓楼里整整一天。“这真的让我很难过,并非由被调用函数分配;

caller’s BP:调用者的栈帧基址,这事关我的名誉。他们说我没干活儿,占用一个指针小的空间,但我很清楚我干了。”诺曼丁说。诺曼丁的故事很有代表性。几十年来,有些情况下会被优化掉。用来将调用路径上所有的栈帧连成一个链表,人们一直在预言机器人将取代人类,方便栈回溯之类的操作。函数通过将栈指针 SP 直接向下移动指定小,来一次性分配 caller’s BP、locals 和 args to callee 所占用的空间,在 x86 架构上就是使用 SUB 指令将 SP 减去指定小;

locals:变量区间,占用若干机器字。用来存放函数的变量,根据函数的变量占用空间小来分配,没有变量的函数不分配;

args to callee:调用传参区域,占用若干机器字。分配空间小,根据当前函数发起的所有的函数调用中,返回值加上参数所占用的空间最的,按此来分配。没有调用任何函数时,不需要分配该区间。在 callee 视角的 args from caller 区间,包含在 caller 视角的 args to callee 区间内,占用空间小是小于等于的关系。

Stack Frame Layout

综上所述,只有 return address 是一定会存在的,其他 3 个区间都要根据实际情况进行分析。

代码示例

下面就结合实际代码,具体说明函数栈帧各区间的分配和使用情况。编译运行如下示例代码:

在笔者使用的 amd64+linux 环境,得到的输出如下所示:

编译时通过指定参数来防止编译器将小函数内联优化掉,那样就不存在真正的栈帧结构了。

3 行输出依次是由 f1、main、f2 中的 println 打印的,所以可以以此为参照,画出栈帧布图。下面先分别进行梳理:

println

代码里之所以使用 println,而没有使用 fmt.Printf 之类的函数,是因为前者更底层更“简单”,不会造成变量逃逸等问题,所以不会带来不必要的干扰。

实际上,代码中的 println 会被编译器转换为多次调用 runtime 包中的 printlock、printunlock、printpointer、printsp、printnl 函数,前两个函数用来进行并发同步,后 3 个用来打印指针、空格和换行,这 5 个函数均无返回值,只有 printpointer 有一个参数。

例如:

会被转换为:

所以这一组函数调用只需要一个机器字的空间,用来向 printpointer 传参。

栈帧布

根据以上的示例代码,以及编译运行的输出,对 3 个函数的栈帧上各区间小进行整理:

对照以上表格,绘制栈帧布图:

Real Stack Layout

左侧是调用 f1 时的运行时栈,右侧是调用 f2 时的运行时栈。

通过 f1 的调用栈,可以发现函数的返回值和参数是按照先返回值后参数,并且是从右至左的顺序在栈上分配的,与 C 语言时期的参数入栈顺序一致。这是因为 f1 的参数和返回值占满了整个 args to callee 区间。

值得注意的是 f2 的调用栈,在 a1 和 v4 之间是空了 3 个机器字的,因为 Go 语言的函数是固定栈帧小,args to callee 是按照所需的最空间来分配。调用函数时,参数和返回值看起来更像是按照先参数后返回值,从左到右的顺序分配在 args to callee 区间里,并且是从低地址开始使用。这点与我们对传统的栈的理解有些不同,更符合传统的栈原理的如 32 位的 VC++编译器,它使用 PUSH 指令动态入栈,args to callee 区间的小不是固定的。Go 这种固定栈帧小的分式,使得像调试、运行时栈扫描之类更易于实现,但是会造成更的栈消耗。

函数的参数和返回值的传递,属于“调用约定”的范畴,是 compiler 和 linker 在进行构建时内遵循的一致性规范。可以参考 C 语言的调用约定,来进行类比学。

在函数内访问运行时栈上的返回值、参数和变量时,通过栈指针 SP 加上相对偏移来寻址。函数栈帧的结构是编译器生成的,就隐含在函数的代码里面,有兴趣的同学可以自己看一下反编译后的汇编代码,自会一目了然。

转自:fengyoulin

fengyoulin.com/func-stack-frame.html

平板电脑衬垫怎么安装视频

edc17更换cpu后怎么刷

怎么查主板规格

古董局中局 付贵去哪了

常克洲字画多少钱一平尺

翡翠脱沙料怎么区分

玉石平面抛光怎么样速度快

虚拟主机可以做代理上网吗

seo编程需要什么代码

免责声明:文中图片均来源于网络,如有版权问题请联系我们进行删除!

标签:栈帧 go语言 调用 编译器 args