Go中理解defer和return顺序

Posted by     "" on Tuesday, December 24, 2019

Go中理解defer和return顺序

我们通过示例来理解deferreturn 之前的到底是怎么运行的.

package main

import "fmt"

func WithoutNameReturnValue() int {
    var i int
    defer func() {
        i++
        fmt.Println("defer2 in WithoutNameReturnValue",i)
    }()

    defer func() {
        fmt.Println("defer1 in WithoutNameReturnValue",i)
    }()
    return i
}

func WithNameReturnValue() (j int) {
    defer func() {
        j++
        fmt.Println("defer2 in WithNameReturnValue", j)
    }()

    defer func() {
        j++
        fmt.Println("defer1 in WithNameReturnValue", j)
    }()

    return j

}

func main() {
    fmt.Println(WithoutNameReturnValue())
    fmt.Println(WithNameReturnValue())
}

执行结果:

defer1 in WithoutNameReturnValue 0
defer2 in WithoutNameReturnValue 1
0
defer1 in WithNameReturnValue 1
defer2 in WithNameReturnValue 2
2

我们发现defer匿名返回值命名返回值函数中的不同表现, 在命名返回值的函数的返回值被defer修改了.
在 Go Tour中, 官方在介绍defer时是这么说的:
A defer statement defers the execution of a function until the surrounding function returns. 经查阅官方资料blog.golang.org,发现defer的执行有以下三个规则:

1: A deferred function’s arguments are evaluated when the defer statement is evaluated. (示例如下:)

// print 0
func a() {
    i := 0
    defer fmt.Println(i)
    i++
    return
}

解析defer 执行的函数如果有传参,defer里面为i的副本.所以print 0, 如果是传递指针会则会改变.

2: Deferred function calls are executed in Last In First Out order after the surrounding function returns.(示例如下)

// This function prints "3210"
func b() {
    for i := 0; i < 4; i++ {
        defer fmt.Print(i)
    }
}

3: Deferred functions may read and assign to the returning function’s named return values.(示例如下)

// This function returns 2
func c() (i int) {
    defer func() { i++ }()
    return 1
}

其中第三点就是文中代码结果为2的原因. 那么为什么结果1defer没有修改返回值呢?我们可以推测,其实在函数最终返回前,defer函数已经执行了,在结果1中,由于defer函数是对函数内部变量i进行操作,所有没有影响.在结果2中,由于返回值被提前声明,所以defer函数能够在return 语句对返回值赋值之后,继续对返回值操作.

函数的整个返回过程是:

  1. return 对返回变量赋值, 如果是匿名返回值就先声明在赋值.
  2. 执行defer 函数
  3. return 携带返回值返回.

换种方式解释下, return返回值的运行机制:

return 并非原子操作,分为赋值返回两步操作
匿名返回值实际上return了一个返回值(假设为s),先将i 赋值给s,后续的操作因为都是针对i进行的,所以不影响s,此后因为s不更新, 所以return s不变。
相当于:

var i int
s := i
return s

命名返回值同上s 相当于命名的变量i, 因为所有的操作都是基于命名变量i(s),返回也是i,所以每一次defer操作,都会更新返回值i.

go-return-defer

「真诚赞赏,手留余香」

Richie Time

真诚赞赏,手留余香

使用微信扫描二维码完成支付