参考文章:深入理解Go语言
defer用于资源的释放,会在函数返回之前进行调用
defer关键字的实现跟go关键字很类似,不同的是它调用的是runtime.deferproc而不是runtime.newproc。
在defer出现的地方,插入了指令call runtime.deferproc,然后在函数返回之前的地方,插入指令call runtime.deferreturn。
普通的函数返回时,汇编代码类似:
add xx SP
return
如果其中包含了defer语句,则汇编代码是:
call runtime.deferreturn,
add xx SP
return
goroutine的控制结构中,有一张表记录defer,调用runtime.deferproc时会将需要defer的表达式记录在表中,而在调用runtime.deferreturn的时候,则会依次从defer表中出栈并执行。
实例一:先按照普通函数执行,然后再按照defer 的先进后出逆向执行
func main() {
fmt.Println("hello world 123")
defer goodBye()
defer goodNight()
goodEating()
fmt.Println("hello world")
}
func goodNight() {
fmt.Println("goodnight")
}
func goodBye() {
fmt.Println("goodbye")
}
func goodEating() {
fmt.Println("goodeating")
}
---------output-----------
hello world 123
goodeating
hello world
goodnight
goodbye
实例二:若在中间添加一个 return,那么在return之上的,先按照普通函数执行,然后按照defer 的先进后出逆向执行,在return之下的不执行
func main() {
fmt.Println("hello world 123")
defer goodBye()
defer goodNight()
goodEating()
return
fmt.Println("hello world")
}
func goodNight() {
fmt.Println("goodnight")
}
func goodBye() {
fmt.Println("goodbye")
}
func goodEating() {
fmt.Println("goodeating")
}
------output-------
hello world 123
goodeating
goodnight
goodbye
实例三:返回值操作
func f() (result int) {
defer func() {
result++
}()
return 0
}
由于 return xxx这一条语句并不是一条原子指令!
所以 return xxx 分为 三步操作 :返回值 = xxx ,调用defer函数,空的return,上面代码可以看作下面的代码
func f() (result int) {
result = 0 //return语句不是一条原子调用,return xxx其实是赋值+ret指令
func() { //defer被插入到return之前执行,也就是赋返回值和ret指令之间
result++
}()
return
}
实例四:带参数的赋值返回值操作
func f() (r int) {
t := 5
defer func() {
t = t + 5
}()
return t
}
上述的return返回的是 t,而我们func的返回值是 r,上面代码可以看作下面的代码
func f() (r int) {
t := 5
r = t //赋值指令
func() { //defer被插入到赋值与返回之间执行,这个例子中返回值r没被修改过
t = t + 5
}
return //空的return指令
}
实例五
func f() (r int) {
defer func(r int) {
r = r + 5
}(r)
return 1
}
分析:
func f() (r int) {
r = 1 //给返回值赋值
func(r int) { //这里改的r是传值传进去的r,不会改变要返回的那个r值
r = r + 5
}(r)
return //空的return
}
实例六
func main() {
i := deferRet(1,1)
println(i) // print 152
}
func deferRet(x,y int) (z int){
defer func () { z += 100 }()
z = x + y
return z + 50 // 执行顺序 z = z+50 -> (call defer)z = z+100 -> ret
}
分析:
func deferRet(x,y int) (z int){
z = x + y // z=2
z = z+50 //给返回值赋值z=52
defer z += 100 //z = 150
return
}
本质原因是return xxx语句并不是一条原子指令,defer被插入到了赋值 与 ret之间,因此可能有机会改变最终的返回值。