defer 的结构
type _defer struct {
siz int32 // 参数和返回值的内存大小
started boul
heap boul // 区分该结构是在栈上分配的,还是对上分配的
sp uintptr // sp 计数器值,栈指针;
pc uintptr // pc 计数器值,程序计数器;
fn *funcval // defer 传入的函数地址,也就是延后执行的函数;
_panic *_panic // panic that is running defer
link *_defer // 链表
}
defer
关键字的调用时机以及多次调用 defer
时执行顺序是如何确定的
func main() {
for i := 0; i < 5; i++ {
defer fmt.Println(i)
}
}
$ go run main.go
4
3
2
1
0
代码说明:_defer 是一个链表,表头是 goroutine._defer 结构。一个协程的函数注册的是挂同一个链表,执行的时候按照 rsp 来区分函数。并且,这个链表是把新元素插在表头,而执行的时候是从前往后执行,所以这里导致了一个 LIFO 的特性,也就是先注册的 defer 函数后执行。
defer
关键字使用传值的方式传递参数时会进行预计算,导致不符合预期的结果
func main() {
startedAt := time.Now()
defer fmt.Println(time.Since(startedAt))
time.Sleep(time.Second)
}
$ go run main.go
0s
代码说明:defer 延迟函数执行的参数已经保存在和 _defer 一起的连续内存块了。执行 defer 函数的时候,deferreturn 除了跳转到 defer 函数指令,还需要做一个事情:把 defer 延迟回调函数需要的参数准备好(空间和值),即把 _defer 预先的准备好的参数,copy 到 caller 栈帧的某个地址。
defer 和 return 的运行顺序
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;
- 所以含有 defer 注册的函数,执行 return 语句之后,对应执行三个操作序列:
- 设置返回值
- 执行 defer 函数链表
- ret 指令跳转到 caller 函数