defer
Go语言中defer的实现主要依赖于两个关键的数据结构:_defer和defer语句在编译期生成的defer指令。 _defer结构体 _defer是一个链表结构,用于存储延迟执行的函数信息。它的定义如下:
type _defer struct {
siz int32 // 参数大小
started bool // 是否已经执行
heap bool // 是否分配在堆上
sp uintptr // 调用方函数的sp寄存器值
pc uintptr // 调用defer的pc
fn *funval // 延迟执行的函数
_panic *_panic // 触发panic时的信息
link *_defer // 链表指针
frameCtor uintptr // 构造函数
frameData uintptr // 构造函数参数
}
其中最关键的字段是fn和link。fn存储了延迟执行函数的信息,link将多个_defer通过链表连接起来。
defer指令
当编译器遇到defer语句时,会生成相应的defer指令,用于在运行时创建_defer结构并将其链接到_defer链表中。 defer指令的执行过程大致如下:
- 计算defer语句中函数的参数值
- 创建一个_defer结构,填充相关字段
- 将新创建的_defer链接到当前goroutine的_defer链表头部
- 当函数返回时,依次从链表头部取出_defer,执行其中的fn函数
如果在执行defer函数时发生panic,则defer函数会暂时终止,等待panic处理完毕后继续执行剩余的defer函数。
defer 和 return 运行顺序
包含 defer 语句的函数返回时,先设置返回值还是先执行 defer 函数?
- 函数的调用过程 要理解这个过程,首先要知道函数调用的过程:
go 的一行函数调用语句其实非原子操作,对应多行汇编指令,包括 1)参数设置, 2) call 指令执行; 其中 call 汇编指令的内容也有两个:返回地址压栈(会导致 rsp 值往下增长,rsp-0x8),callee 函数地址加载到 pc 寄存器; go 的一行函数返回 return语句其实也非原子操作,对应多行汇编指令,包括 1)返回值设置 和 2)ret 指令执行; 其中 ret 汇编指令的内容是两个,指令 pc 寄存器恢复为 rsp 栈顶保存的地址,rsp 往上缩减,rsp+0x8; 参数设置在 caller 函数里,返回值设置在 callee 函数里; rsp, rbp 两个寄存器是栈帧的最重要的两个寄存器,这两个值划定了栈帧; 最重要的一点:Go 的 return 的语句调用是个复合操作,可以对应一下两个操作序列:
- 设置返回值
- ret 指令跳转到 caller 函数
- return 之后是先返回值还是先执行 defer 函数? Golang 官方文档有明确说明:
That is, if the surrounding function returns through an explicit return statement, deferred functions are executed after any result parameters are set by that return statement but before the function returns to its caller.
也就是说,defer 的函数链调用是在设置了返回值之后,但是在运行指令上下文返回到 caller 函数之前。
所以含有 defer 注册的函数,执行 return 语句之后,对应执行三个操作序列:
- 设置返回值
- 执行 defer 链表
- ret 指令跳转到 caller 函数
那么,根据这个原理我们来解析如下的行为:
func f1 () (r int) {
t := 1
defer func() {
t = t + 5
}()
return t
}
func f2() (r int) {
defer func(r int) {
r = r + 5
}(r)
return 1
}
func f3() (r int) {
defer func () {
r = r + 5
} ()
return 1
}
这三个函数的返回值分别是多少?
答案:f1() -> 1,f2() -> 1,f3() -> 6 。
a) 函数 f1 执行 return t 语句之后:
设置返回值 r = t,这个时候局部变量 t 的值等于 1,所以 r = 1; 执行 defer 函数,t = t+5 ,之后局部变量 t 的值为 6; 执行汇编 ret 指令,跳转到 caller 函数; 所以,f1() 的返回值是 1 ;
b) 函数 f2 执行 return 1 语句之后:
设置返回值 r = 1 ; 执行 defer 函数,defer 函数传入的参数是 r,r 在预计算参数的时候值为 0,Go 传参为值传递,0 赋值给了匿名函数的参数变量,所以 ,r = r+5 ,匿名函数的参数变量 r 的值为 5; 执行汇编 ret 指令,跳转到 caller 函数; 所以,f2() 的返回值还是 1 ;
c) 函数 f3 执行 return 1 语句之后:
设置返回值 r = 1; 执行 defer 函数,r = r+5 ,之后返回值变量 r 的值为 6(这是个闭包函数,注意和 f2 区分); 执行汇编 ret 指令,跳转到 caller 函数; 所以,f1() 的返回值是 6 ;
defer 闭包捕获的变量和常量是引用传递不是值传递
for i := 0; i < 3; i++ {
defer func() {
fmt.Println(i)
}()
}
3
3
3
for i := 0; i < 3; i++ {
item := i
defer func() {
fmt.Println(item)
}()
}
2
1
0
// 作为函数参数
for i := 0; i < 3; i++ {
defer fmt.Println(i)
}
2
1
0
func main() {
x := 10
defer fmt.Println("Deferred:", x) // x 的值在这里被捕获
x = 20 // 修改 x 的值
fmt.Println("Before defer:", x)
}
/*
Before defer: 20
Deferred: 10
*/