defer 需要注意的2点
-
defer 函数的参数在defer 语句出现时就已经确定
func foo() { i := 0 defer fmt.Println(i) i++ return }
输出 i 的值为0
-
defer 函数可以操作所在函数的具名返回值
关键字return
并不是一个原子操作,实际上 return 只代表汇编指令ret
,即跳转程序执行,比如语句return i
,实际上分为2步执行,即先将i 的值存入栈中作为返回值,然后执行跳转,而defer 语句的执行时机正是在跳转前。func foo() (result int) { i := 1 defer func() { result++ } return i }
所以这里return 执行的代码等同于:
result = i result++ return
数据结构
src/runtime/runtime2.go:_defer
定义了 defer
数据结构
type _defer struct {
...
sp uintptr // 函数栈指针
pc uintptr // 程序计数器
fn *funcval // 函数地址
link*_defer // 指向自身结构的指针,用于形成链表
}
从其数据结构来看,每个_defer
实例实际上是对一个函数的封装,它拥有执行函数的必要信息,比如栈地址,程序计数器,函数地址等。编译器会把每个延迟函数编译成一个_defer
实例暂存到数据结构中,待函数结束时再逐个取出执行。
每个 defer 语句对应一个 _defer
实例,多个实例使用指针链接起来形成一个单链表(头部插入),保存到 数据结构中。
type g struct {
...
_defer
...
}

defer 的创建和执行
src/runtime/panic.go
定义了一下两个方法,分别用于创建 defer 和 执行 defer
-
deferproc
负责把 defer 函数处理成_defer
实例,并存入 gorountine 中的链表。 -
deferreturn
负责把 从 defer 从 gorountine 链表中的实例取出并执行。
演进和优化历史
TODO
网友评论