defer 的作用
在golang中提供了defer关键字,在一个函数中可以去注册多个延迟调用,这些调用以先进后出(FILO)的顺序在函数返回前被执行,可以保证一些资源可以被回收或释放。但是如果错误的使用defer去处理返回值,可能会有一些意想不到的后果。
例子
// 带命名返回值的函数
func f1() (r int) {
defer func() {
r++
}()
return 1
}
// 不带命名返回值的函数
func f2() int {
t := 1
defer func() {
t ++
}()
return t
}
func TestF1(t *testing.T) {
fmt.Println(f1()) // r = 2
}
func TestF2(t *testing.T) {
fmt.Println(f2()) // r = 1
}
首先我们需要明确两点
- 函数调用方负责开辟栈空间,包括形参和返回值的空间。
- 有名的函数返回值相当于函数的局部变量,被初始化为类型的零值。
在这里解释一下 f1
- r 是函数的有名返回值,分配在栈上,初始化为 0。
- return 1 会将 1 复制到返回值栈区,r被赋值为 1。
- 执行defer语句,由于匿名函数对返回值r是闭包引用,所以r++执行后,函数返回值被修改为 2。
- defer 执行完后 RET 返回,此时函数的返回值为 2。
在看一下 f2, 他与 f1 有什么不同?
- 引入局部变量 t = 1
- 复制 t 的值到返回值(r)所在的栈区
- defer 语句的匿名函数对局部变量 t 做修改, t = 2
- 函数返回, 返回值(r)仍然为 1