defer执行规则
规则1: 延迟函数的参数在defer语句出现时就已经确定下来了
规则2: 延迟函数执行按后进先出顺序执行,即先出现的defer最后执行
规则3: 延迟函数可能操作主函数的具名返回值
对规则1的说明:
延迟函数的参数相当于是对变量的copy
延迟函数的参数是值类型的变量的话,后面对该变量的操作不影响defer 中参数变量
延迟函数的参数是指针类型(引用类型
)的变量的话,后面对该指针变量的操作不影响defer 中参数变量
func printArray(array []int) {
fmt.Printf("printArray &array %p\n", &array) //array变量的地址
fmt.Printf("printArray array %p\n", array) //array变量保存的地址切片的内存地址
fmt.Println("printArray &array【0】", &array[0]) //切片第一个元素的地址
//array变量保存的地址切片的内存地址 和 切片第一个元素的地址是相同的
for i := range array {
fmt.Println((array)[i])
}
}
func deferFuncParameter() {
//引用类型的变量实际上就是存储地址值的变量
var aArray = []int{1, 2, 3}
//aArray本身是一个切片变量(引用类型的变量),存的内容是一个地址值
fmt.Printf("deferFuncParameter &aArray %p \n", &aArray) //引用类型的变量的本身地址
fmt.Printf("deferFuncParameter aArray %p \n", aArray) //切片的内存地址
//func printArray(array []int)
defer printArray(aArray) //传递aArray变量,相当于是copy变量的值(变量的值是一个地址),copy给array这个切片变量
// aArray中的地址和array中的地址是同一个地址
aArray[0] = 10
return
}
func main() {
deferFuncParameter()
}
执行结果
deferFuncParameter &aArray 0xc000114060
deferFuncParameter aArray 0xc00012c078
printArray &array 0xc000114090
printArray array 0xc00012c078
printArray &array【0】 0xc00012c078
10 //由于指向的同一片内存空间,共享内存,修改的话,影响defer的结果
2
3
对规则2的说明
定义defer类似于入栈操作,执行defer类似于出栈操作。
设计defer的初衷是简化函数返回时资源清理的动作,资源往往有依赖顺序,比如先申请A资源,再根据A资源申请B资源,根据B资源申请C资源,即申请顺序是:A–>B–>C,释放时往往又要反向进行。 这就是把defer设计成LIFO的原因。
每申请到一个用完需要释放的资源时,立即定义一个defer来释放资源是个很好的习惯。
对规则3的说明
return
不是一个原子性的操作,分为两步执行,第一步给返回值赋值,第二步执行ret指令进行跳转。
defer语句后的函数执行再ret跳转之前执行,可能会修改返回值。
func main() {
fmt.Println("foo():", callbacefunc(foo())) // foo(): 2
}
func callbacefunc(x func() int) int {
return x()
}
func foo() func() int {
var i = 1
return func() (x int) {
defer func() {
x++
}()
return i
}
}
以下两种情况:defer后面的函数执行均无法影响返回值
- 主函数拥有匿名返回值,返回字面值(字面量:1,2 ,“xxx”)
- 主函数拥有匿名返回值,返回变量