go语言中defer用于在退出当前函数前执行某些特定逻辑,比如进行close/unlock
动作,defer中函数入参传递和函数调用入参传递一样都是值传递,也就是会立即复制外部引用的入参。
defer结构体如下:
type _defer struct {
siz int32 // 参数和结果的内存大小;
started bool
openDefer bool
sp uintptr
pc uintptr // 分别代表栈指针和调用方的程序计数器
fn *funcval // 关键字中传入的函数
_panic *_panic // 是触发延迟调用的结构体,可能为空;
link *_defer
}
所有defer函数都会通过通过link字段串联成链表(每个goroutinue有一个_defer链表,defer函数执行类似于栈结构的FIFO方式,最新defer的函数最先被调用):
为什么defer要设计成类似栈结构的执行方式呢?这其实是和程序中函数调用执行机制-栈结构契合,如果不设计成栈调用方式,那么就需要记录或者从头遍历直到当前函数帧对应的defer函数才行,不如按照栈的FIFO机制来的简单。
goroutine 会保存 defer 调用链,函数 return 时候会判断 goroutine defer 链中的 sp 和当前 sp 关系,如果符合条件就执行 defer 结构体中保存的函数指针,参数也是通过 defer 结构体保存的传递过去的。
如果defer执行可以在编译期确定,会在函数return前直接插入对应的代码,否则会由runtime.returndefer来执行。
defer函数中变量有2类,一种是defer函数入参,另一种是defer函数内使用了外部变量。前者和普通的函数调用传递是一样的,这里不在细讲;后者其实就是闭包引用,不过只有到defer函数真正执行时才能根据上下文确定确定当前值(go闭包引用和Java中实现机制不一样,Java中引用外部变量后该外部变量就不能变更了,相当于final变量),因此defer中的闭包引用变量只有到函数return时才能知道到底是什么数据。
func main() {
Func()
}
func Func() {
a := 1
defer func() {
fmt.Println("first defer:", a)
}()
a = 2
defer func() {
fmt.Println("second defer:", a)
}()
defer func() {
fmt.Println("third defer:", a)
a = 3 // 更新a的值会影响到后续defer引用a
}()
}
// 结果输出:
third defer: 2
second defer: 3
first defer: 3
return执行经过defer时会将return xxx
这一句语句编译为如下3个语句:
返回值 = xxx
调用defer函数
空的return
因此,如果函数return某个指针变量并且defer中将其变更的话,会导致返回defer变更后的值,不太符合一般认知,对于非指针类则无影响。