函数栈帧
- 我们知道,Golang可以把程序直接编译成
.exe
可执行文件,运行时,可执行文件被加载到内存,这些个机器指令对应到虚拟地址空间中,位于代码段(虚拟地址空间分别有:栈、堆、数据段、代码段)。 - 在这其中,如果在一个函数调用另一个函数,编译器就会对应生成一条call指令,每个函数最后都有一条ret指令,负责函数结束后跳回到调用处。
- 函数执行时,需要有足够的空间存放局部变量、参数、返回值等信息,这段空间对应虚拟地址空间中的栈。分配给函数的空间叫做函数栈帧,这就是咱们的主角了。栈底通常叫栈基bp,栈顶又叫栈指针sp,整个栈帧中(仅谈Golang),从底到顶依次为:栈基、局部变量,返回值、参数、栈指针。
call与ret
call指针只做两件事:
- 将下一条指令入栈,即返回地址,被调用函数执行结束后可直接跳回这里继续执行
- 跳转到被调用函数入口处开始执行(被调用者是通过栈指针sp+偏移找到对应指令进行执行的)
ret指令同样做两件事:
- 弹出返回地址
- 跳转到返回地址
defer与返回值
- 返回值赋值和defer谁先谁后呢?答:先给返回值赋值,再执行defer函数。
- 如果返回值是匿名类型
func Method() int {}
,Golang的函数栈帧中返回值是用来存放其值result
的,即使return a
,也相当于将a的值拷贝给返回值resultresult = a
,所以再defer中对a进行操作,是不会影响返回值result的结果的。 - 如果返回值是已命名类型
func Method() (a int) {}
,则就相当于对函数栈帧中返回值这一空间重新命名了,这时候你在defer中操作这个a,是会影响最终的返回结果的。