golang's defer analysis

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
}


Don't run the code yet, run the results in your mind. Then go to verify. If all three are done right and it's not deceptive.... well, don't look down, you already understand defer.








Spare a few lines to make sure you run through the code in your head first, then verify it, and have doubts...

Well, if you count 0 in example1, you are wrong

If you think it's 10 in example2, you're wrong again...it doesn't count...

If example3 thinks it's 6, you're wrong again... If you're right and wrong, well... you're just fooling!

If you don't understand, keep reading...


The first thing to be clear is: defer is executed before return

This is clearly stated in the official documentation http://golang.org/ref/spec#Defer_statements, just know it, you can ignore it


Then to understand how defer is implemented, I have previously written http://bbs.mygolang.com/thread-271-1-1.html

The effect is the instruction inserted where defer appears

CALL runtime.deferproc

Then insert the instruction just before the function returns

CALL runtime.deferreturn


Then, the way to specify the return value of go is different from that of C. In order to support multi-value return, go uses the stack to return the value, while C uses the register.


The most important point is: the statement return xxx is not an atomic instruction!

整个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)
}

确实是这样


Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=324698721&siteId=291194637