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的参数和返回值可以把这里占满没有问题


目录
相关文章
|
2月前
|
Unix 程序员 编译器
第六章 Golang函数
第六章 Golang函数
33 0
|
2月前
|
Go
golang数据结构篇之栈和队列以及简单标准库
golang数据结构篇之栈和队列以及简单标准库
42 0
|
2月前
|
Go
golang中置new()函数和make()函数的区别
golang中置new()函数和make()函数的区别
|
2月前
|
Go
Golang深入浅出之-Go语言函数基础:定义、调用与多返回值
【4月更文挑战第21天】Go语言函数是代码组织的基本单元,用于封装可重用逻辑。本文介绍了函数定义(包括基本形式、命名、参数列表和多返回值)、调用以及匿名函数与闭包。在函数定义时,注意参数命名和注释,避免参数顺序混淆。在调用时,要检查并处理多返回值中的错误。理解闭包原理,小心处理外部变量引用,以提升代码质量和可维护性。通过实践和示例,能更好地掌握Go语言函数。
37 1
Golang深入浅出之-Go语言函数基础:定义、调用与多返回值
|
2月前
|
JSON Go 数据格式
golang学习7,glang的web的restful接口结构体传参
golang学习7,glang的web的restful接口结构体传参
|
2月前
|
JSON Go 数据格式
golang学习6,glang的web的restful接口传参
golang学习6,glang的web的restful接口传参
|
2月前
|
负载均衡 监控 Go
Golang深入浅出之-Go语言中的服务网格(Service Mesh)原理与应用
【5月更文挑战第5天】服务网格是处理服务间通信的基础设施层,常由数据平面(代理,如Envoy)和控制平面(管理配置)组成。本文讨论了服务发现、负载均衡和追踪等常见问题及其解决方案,并展示了使用Go语言实现Envoy sidecar配置的例子,强调Go语言在构建服务网格中的优势。服务网格能提升微服务的管理和可观测性,正确应对问题能构建更健壮的分布式系统。
41 1
|
2月前
|
JSON 监控 安全
Golang深入浅出之-Go语言中的反射(reflect):原理与实战应用
【5月更文挑战第1天】Go语言的反射允许运行时检查和修改结构,主要通过`reflect`包的`Type`和`Value`实现。然而,滥用反射可能导致代码复杂和性能下降。要安全使用,应注意避免过度使用,始终进行类型检查,并尊重封装。反射的应用包括动态接口实现、JSON序列化和元编程。理解反射原理并谨慎使用是关键,应尽量保持代码静态类型。
38 2
|
2月前
|
存储 Go 开发者
Golang深入浅出之-Go语言字符串操作:常见函数与面试示例
【4月更文挑战第20天】Go语言字符串是不可变的字节序列,采用UTF-8编码。本文介绍了字符串基础,如拼接(`+`或`fmt.Sprintf()`)、长度与索引、切片、查找与替换(`strings`包)以及转换与修剪。常见问题包括字符串不可变性、UTF-8编码处理、切片与容量以及查找与替换的边界条件。通过理解和实践这些函数及注意事项,能提升Go语言编程能力。
39 0
|
2月前
|
C++ Go Rust
Golang每日一练(leetDay0082) 用队列实现栈、用栈实现队列
Golang每日一练(leetDay0082) 用队列实现栈、用栈实现队列
39 0
Golang每日一练(leetDay0082) 用队列实现栈、用栈实现队列