在golang中,对于defer,我之前的理解就是和java中的finally代码块一样,没什么难度,但是吧,当我最近看的一些神奇的问题,我就发现原来并非想的那么简单。
package main
import "fmt"
func main() {
fmt.Println(DeferFunc1(1))
fmt.Println(DeferFunc2(1))
fmt.Println(DeferFunc3(1))
DeferFunc4()
}
func DeferFunc1(i int) (t int) {
t = i
defer func() {
t += 3
}()
return t
}
func DeferFunc2(i int) int {
t := i
defer func() {
t += 3
}()
return t
}
func DeferFunc3(i int) (t int) {
defer func() {
t += i
}()
return 2
}
func DeferFunc4() (t int) {
defer func(i int) {
fmt.Println(i)
fmt.Println(t)
}(t)
t = 1
return 2
}
请问这段代码输出的结果是什么? 答案见文末 如果你看完答对了,那么请直接点击右上角的关闭按钮,如果你答错了,你可以继续往下看了。 下面会一步步介绍,到底为什么结果会是这样
如 : func DeferFunc1(i int) (t int) {
其中返回值t int,这个t会在函数起始处被初始化为对应类型的零值并且作用域为整个函数。
虽然这边没有提及,但是还是要说一下,因为很多人学习defer的时候都会用到,就是当多个defer出现的时候,它是一个“栈”的关系,也就是先进后出。一个函数中,写在前面的defer会比写在后面的defer调用的晚。
return先,defer后 这个可能会让人怀疑,后面会详细解释。
在没有defer的情况下,其实函数的返回就是与return一致的,但是有了defer就不一样了。 函数的返回其实是有两个步骤的,第一个当执行到return语句的时候
func DeferFunc3(i int) (t int) {
defer func() {
t += i
}()
return 2
}
这个时候会先将返回值t赋值为2,然后执行defer,完成之后才会真正返回外部调用者。
这个就是今天的重头戏了,defer这个语法其实一共有三个步骤。
有了上面的所有知识点,其实你就应该能明白上面输出的结果了。如果还不明白就看看下面的分析解释吧。
func DeferFunc1(i int) (t int) {
t = i
defer func() {
t += 3
}()
return t
}
首先上面是第一个方法
func DeferFunc2(i int) int {
t := i
defer func() {
t += 3
}()
return t
}
第二个方法
可能这里就有点难理解了,修改一下代码你就明白了
func DeferFunc2(i int) (result int) {
t := i
defer func() {
t += 3
}()
return t
}
上面的代码return的时候相当于将t赋值给了result,当defer修改了t的值之后,对result是不会造成影响的。
func DeferFunc3(i int) (t int) {
defer func() {
t += i
}()
return 2
}
func DeferFunc4() (t int) {
defer func(i int) {
fmt.Println(i)
fmt.Println(t)
}(t)
t = 1
return 2
}
这个分析的步骤要详细一些
那么 defer 在底层究竟是如何实现的呢? 通过生成汇编代码我们可以看到下面这样的方法: CALL runtime.deferproc(SB) CALL runtime.deferreturn(SB) 实际上来说当我们使用defer的使用就会调用runtime.deferproc,那么这个时候,就会将所有的参数赋值好,所有就像我们上面例子中看到的一样,在调用defer的时候参数会先计算好保存起来,然后挂载到G._defer中,最后deferreturn的时候进行执行相关的defer中的方法
func deferproc(siz int32, fn *funcval) { // arguments of fn follow fn
sp := getcallersp(unsafe.Pointer(&siz))
argp := uintptr(unsafe.Pointer(&fn)) + unsafe.Sizeof(fn)
callerpc := getcallerpc(unsafe.Pointer(&siz))
systemstack(func() {
d := newdefer(siz)
})
d.fn = fn
d.pc = callerpc
d.sp = sp
memmove(add(unsafe.Pointer(d), unsafe.Sizeof(*d)),
unsafe.Pointer(argp), uintptr(siz))
// deferproc returns 0 normally.
// a deferred func that stops a panic makes the deferproc return 1.
// the code the compiler generates always checks the return value and jumps to the
// end of the function if deferproc returns != 0.
return0()
}
看完,有的人肯定又要出来搞事了,说这个在实际中不会遇到的,实际中谁写这么蠢的代码。但是其实某些时候非常重要,当我们需要在defer中返回一些错误信息的时候,并且需要将这些信息给到调用者的时候,就需要注意变量的作用域以及执行顺序所带来的差异。
而且正因为这样的执行顺序,在实际中要记住: defer 最大的功能是 panic 后依然有效 所以defer可以保证你的一些资源一定会被关闭,从而避免一些异常出现的问题。
参考例子来源于网络,自己做了修改和结合: https://stackoverflow.com/questions/52718143/is-golang-defer-statement-execute-before-or-after-return-statement
4 1 3 0 2