其他分享
首页 > 其他分享> > 浅谈Golang函数调用栈-传参和返回值

浅谈Golang函数调用栈-传参和返回值

作者:互联网

函数调用栈-传参和返回值

defer与return时机

return赋值和返回是两个步骤,不是原子操作,如果有defer会插在两个步骤中:

  1. 返回值赋值(return value)
  2. defer语句 //可有可无
  3. 返回值返回

传值的swap函数

在这里插入图片描述
我们通过函数调用栈看看问题到底出在哪
在这里插入图片描述

假设main函数栈帧在这里,先分配局部变量locals,这里函数调用没有返回值,所以局部后面就是给被调用函数传入的参数args,注意参数入栈顺序由右到左,返回值也是一样,这样被调用函数通过sp+偏移寻址就比较方便了,调用者栈帧后面存的是下一条指令的地址,在下面分配的就是swap函数栈帧了,当swap函数执行到a,b=b,a时,要交换两个参数的值
在这里插入图片描述
把值交换一下
在这里插入图片描述
现在,交换失败的原因找到了,调用者的局部变量a和b在这里,交换的并不是它们

传指针的swap函数

在这里插入图片描述
我们通过函数调用栈看看和上一次有什么不同
在这里插入图片描述

main函数栈帧先分配局部变量,然后分配参数空间,参数是指针,传参都是值拷贝,这里拷贝的就是a和b的地址,在后面是返回地址以及swap函数栈帧

在这里插入图片描述
swap执行到*a,*b=*b,*a时,交换的是这两个指针指向的数据,也就是这两个地址的数据,所以这一次能交换成功

匿名返回值函数

通常我们认为返回值是通过寄存器传递的,但是go语言支持多返回值,所以在栈上分配返回值空间更合适
在这里插入图片描述
这里main函数调用incr函数,然后赋给局部变量b,来看看函数调用栈的情况。
在这里插入图片描述

main函数栈帧,先是局部变量a=0,b=0,然后是incr的返回值,初始化为类型零值,然后是参数,传参值拷贝,最后是返回地址。到incr函数栈帧这里,保存调用者main的栈帧地址后,初始化局部变量b
在这里插入图片描述
执行到这里,要把参数a自增1,而参数a在这
在这里插入图片描述
下一步,把参数a赋给局部变量b。到return这里,必须要明确一个关键问题。我们说过函数最后有编译器插入的指令,负责释放函数栈帧,恢复到调用者栈,但在这之前要给返回值赋值并执行defer函数,那谁先?谁后?答案是先赋值
在这里插入图片描述
所以执行到return b这里,会先把局部变量b的值拷贝到返回值空间
在这里插入图片描述
然后再执行注册的defer函数,defer函数里,这一步a再次自增1,下一步局部变量b也自增1,然后incr结束。
在这里插入图片描述
返回值为1 ,赋给main函数的局部变量b,所以最后会输出0和1

具名返回值函数

在这里插入图片描述
其他都不变,只把这里的局部变量b,改成命名返回值,看看有什么不同

在这里插入图片描述
main函数栈帧与上一例完全相同,到incr函数栈帧这里,没有局部变量,当执行到a++时,参数a自增1
在这里插入图片描述
return这里,先把参数a赋给返回值b

在这里插入图片描述
然后执行defer函数,参数a再次自增1,下一步,返回值b也自增1,然后incr结束,返回值最终为2,所以main的局部变量b赋值为2,最终输出0和2

调用多个函数的小问题

在这里插入图片描述

如果一个函数A调用了两个函数B和C。但是这两个函数的参数和返回值,占用的空间并不相同,我们知道Go语言的函数栈帧是一次性分配的,如果局部变量占这么大,这后面还要以最大的参数加返回值空间为标准来分配,才能满足所有被调函数的需求
在这里插入图片描述
B的参数和返回值可以把这里占满没有问题
在这里插入图片描述
但是B结束后调用C时,它的参数和返回值,只会占用下面这段空间,虽然上面空出来一块,但是被调用者,通过栈指针相对寻址自己的参数和返回值时会比较方便

标签:浅谈,局部变量,函数调用,Golang,参数,返回值,栈帧,函数
来源: https://blog.csdn.net/qq_42956653/article/details/121065713