golang的defer精析

example1

func f() (result int) { 
    defer func() { 
        result++ 
    }() 
    return 0
}


example2

func f() (r int) { 
    t := 5 
    defer func() { 
        t = t + 5 
    }() 
    return t
}


example3

func f() (r int) { 
    defer func(r int) { 
        r = r + 5 
    }(r) 
    return 1
}


先不要运行代码,自己在心里跑一遍结果。然后再去验证。如果三个都做对了并且不是蒙的....好吧,不用往下看了,你已经懂defer了。








多空几行确保你先在心里跑过一遍代码,之后验证了,并且存在疑惑......

额,如果example1中你算的是0,你就错了

如果example2中你觉得是10,你又错了...蒙对的不算...

如果example3中觉得得6,你又错了...如果你有算对的,也有算错,好吧...你丫的就是在蒙!

不懂的继续往下看啊.....


首先要明确的是:defer是在return之前执行的

这是官方文档中明确说明的http://golang.org/ref/spec#Defer_statements ,知道就行了,可以无视


然后要了解是的defer的实现方式,我以前有写过http://bbs.mygolang.com/thread-271-1-1.html

大意就是在defer出现的地方插入的指令

CALL runtime.deferproc

然后在函数返回之前的地方,插入指令

CALL runtime.deferreturn


再就是明确go返回值的方式跟C是不一样的,为了支持多值返回,go是用栈返回值的,而C是用寄存器。


最最最重要的一点就是:return xxx 这一句语句并不是一条原子指令!

整个return过程,没有defer之前是,先把在栈中写一个值,这个值被会当作返回值。然后再调用RET指令返回。return xxx语句汇编后是先给返回值赋值,再做一个空的return: ( 赋值指令 + RET指令)

defer的执行是被插入到return指令之前的

有了defer之后,就变成了 (赋值指令 + CALL defer指令 + RET指令)

而在CALL defer函数中,有可能将最终的返回值改写了...也有可能没改写。总之,如果改写了,那么看上去就像defer是在return xxx之后执行的~

这是所有你所想不明白的defer故事发生的根源。


上面的基础知识都有了,然后就可以来说说神奇的defer了。告诉大家一个简单的转换规则大家就再也不为defer迷糊了。

改写规则是将return语句分开成两句写,return xxx会被改写成:

返回值 = xxx

调用defer函数

空的return


先看example1。它可以改写成这样:

func f() (result int) { 

    result = 0 //return语句不是一条原子调用,return xxx其实是赋值+RET指令 

    func() { //defer被插入到return之前执行,也就是赋返回值和RET指令之间 

        result++ 

    }() 

    return

}

所以这个返回的是1


再看example2。它可以改写成这样:

func f() (r int) { 

    t := 5 

    r = t //赋值指令 

    func() { //defer被插入到赋值与返回之间执行,这个例子中返回值r没被修改过 

        t = t + 5 

    } 

    return //空的return指令

}

所以这个的结果是5


最后看example3。它改写后变成:

func f() (r int) { 

    r = 1 //给返回值赋值 

    func(r int) { //这里改的r是传值传进去的r,不会改变要返回的那个r值 

        r = r + 5 

    }(r) 

    return //空的return

}

所以这个例子结果是1


懂了么?

结论:defer确实是在return之前调用的。但表现形式上却可能不像。本质原因是return xxx语句并不是一条原子指令,defer被插入到了赋值 与 RET之前,因此可能有机会改变最终的返回值。

当你觉得迷糊时,可以用我给的这套规则转一下代码。



polaris · #1 · 3年之前

注:如果函数不是命名返回值,没有这样的困惑。

qkb_75_go
qkb_75_go · #2 · 3年之前

GO 的 return 过程,好像 LUA 语言呀。 只不过 LUA 不支持 defer 。

gcweb
gcweb · #3 · 3年之前

特别注册了来点个赞

daemon_w
daemon_w · #4 · 3年之前

对第三个有点困惑,defer内的r不是返回值的引用么?难道是被defer的参数r覆盖掉了吗?

polaris
polaris · #5 · 3年之前
对 daemon_wdaemon_w #4 回复

go 函数调用,按值传递的。defer内的 r 跟命名返回值完全没关系了。

lcplj123
lcplj123 · #6 · 2年之前

为了点赞,特意注册了个号。

JY115
JY115 · #7 · 2年之前

func f3() (r int) {
    defer func(i int) {
        fmt.Println("r:", r)
        fmt.Println("i:", i)
        i = i + 5
        fmt.Println("f3():", i)
    }(r)
    fmt.Println("r:", r)
    return 1
}

调用上面函数f3()时,fmt.Println("i:", i)的打印结果是i:0,为什么不是i:1呢?

AntonydasWang
AntonydasWang · #8 · 2年之前

@JY115 语句调用顺序如下:

 fmt.Println("r:", r) // 此时 r 还是空的,即为0值
 r = 1
 func(r) // defer func()里面的语句不能改变 r 的值
 return

JY115
JY115 · #9 · 2年之前

我是想问函数f3()里的defer func()是匿名函数,r是实参,i是形参,其中的fmt.Println("i:", i)输出结果不应该是i:1吗?

TodayyadoT
TodayyadoT · #10 · 5月之前
对 JY115JY115 #9 回复

我觉得应该是这样的:

1.fmt.Println("r:", r) // 此时 r 还是空的,即为0值
2.r = 1
3.func(0)// 与楼上的区别在这里,从结果来看,这里r=0, r的值是在defer语句所在的地方时决定里,所以defer的是func(0),而不是defer的func(r)
4.return

只是对应结果的猜测,求大神给标准答案。

TodayyadoT
TodayyadoT · #11 · 5月之前
对 JY115JY115 #9 回复

func f3(){
    i := 1
    defer fmt.Printf("1:: %v\n",i)
    i = 2
    defer fmt.Printf("2:: %v\n",i)
    i = 8
    defer fmt.Printf("3:: %v\n",i)
    fmt.Printf("4:: %v\n",i)
}

确实是这样


阅读更多

没有更多推荐了,返回首页