Golang中的defer语句在return语句之前还是之后执行?

24

我有一个关于golang延迟语句的问题:在golang中,defer语句是在return语句之前还是之后执行?

我已经阅读了Defer_statements,但我没有找到答案。

我进行了一个简单的测试:

func test1() (x int) {
    defer fmt.Printf("in defer: x = %d\n", x)

    x = 7
    return 9
}

func test2() (x int) {
    defer func() {
        fmt.Printf("in defer: x = %d\n", x)
    }()

    x = 7
    return 9
}

func test3() (x int) {
    x = 7
    defer fmt.Printf("in defer: x = %d\n", x)
    return 9
}

func main() {
    fmt.Println("test1")
    fmt.Printf("in main: x = %d\n", test1())
    fmt.Println("test2")
    fmt.Printf("in main: x = %d\n", test2())
    fmt.Println("test3")
    fmt.Printf("in main: x = %d\n", test3())
}

test1()中,在defer之后使用Printf打印x。 在test2()中,使用匿名函数在defer之后打印x。 在test3()中,在x = 7之后再defer,使用Printf打印x。

但结果是:

test1
in defer: x = 0
in main: x = 9
test2
in defer: x = 9
in main: x = 9
test3
in defer: x = 7
in main: x = 9

那么,有人能解释一下吗: 1. 为什么会得到这个结果?为什么test1打印0,test2打印9,test3打印7。 2. 延迟语句是在返回之后执行还是在返回之前执行?

非常感谢。

8个回答

29

感谢 @dev.bmax @Tranvu Xuannhat @rb16 的帮助,通过你们的协助,我从 Defer_statements 中找到了关键的解释。

每次执行 "defer" 语句时,函数值和调用的参数会像平常一样被评估并重新保存,但实际的函数不会被调用。

我们可以将 defer ... 分成三个部分:

  1. 调用 defer ,评估函数参数的值。
  2. 执行 defer ,将函数推入栈中。
  3. 在返回或 panic 后执行堆栈中的函数。

我创建了一个新的 test4 来进行解释。

func test4() (x int) {
    defer func(n int) {
        fmt.Printf("in defer x as parameter: x = %d\n", n)
        fmt.Printf("in defer x after return: x = %d\n", x)
    }(x)

    x = 7
    return 9
}

在test4中,

  1. 调用defer语句,计算n的值,n = x = 0,所以参数x为0。
  2. 执行defer语句,将func(n int)(0)推入堆栈。
  3. return 9之后执行func(n int)(0)fmt.Printf("in defer x as parameter: x = %d\n", n)中的n已经被计算出来了,现在将计算fmt.Printf("in defer x after return: x = %d\n", x)中的x,x为9。

因此,得到结果:

test4
in defer x as parameter: x = 0
in defer x after return: x = 9
in main: x = 9

5

这与时间先后无关,而是与堆栈中是否存在defer语句有关。如果存在(当控制流到达defer语句时,它会将语句保存在堆栈中),则在return语句之后执行。

例如 -

func testing() err {
    x, err := doSomething();
    if err != nil {
       return;
    }
    defer fmt.Println("hello")
    return nil
}

如果err != nil,这个defer语句将永远不会执行,因为程序在控制流到达defer语句之前已经返回。

现在来看你的问题(所有都将到达defer语句) 1. 情况,存储在堆栈中的语句是defer fmt.Printf("in defer: x = %d\n", x)(带有零值的x)

  1. 情况- 存储在堆栈中的语句是一个函数

defer func() { fmt.Printf("in defer: x = %d\n", x) }()

它依赖于x(请注意,在堆栈中,defer语句是一个函数,函数的值将在函数执行时分配)

  1. 情况- defer fmt.Printf("in defer: x = %d\n", x),其值为7

5
在第一次测试中,当包含defer的行运行时,参数x的值将被评估。而且在第1行发生时,x仍为0。
请注意,即使稍后会调用fmt.Printf,它的参数也会提前评估。
在第三个测试中,在defer语句运行之前,参数x的值被设置为7。同样,在实际调用fmt.Printf之前发生了这种情况。
第二个测试有点不同。变量x在匿名函数的作用域内。当函数运行时,它的值被评估,这发生在测试函数返回时。那时,x为9。

5
我将参考Defer语句来解释您的结果。
问题2:defer执行于return之后。
问题1:根据文档:
每次执行 "defer" 语句时,函数值和调用参数会像平常一样被求值并被保存,但实际的函数却没有被调用。
测试1:Defer调用fmt.Println。fmt.Println评估x的值,该值为0(零值)。test1()在main函数中返回9。
测试2:Defer调用func() {}。fmt.Println只有在执行func() {}之后(即return之后)才会评估x。因此,x被评估为9。test2()在主函数中返回9。
测试3:Defer调用fmt.Println。fmt.Println评估x的值,该值为7(x被赋值为7)。test3()在main函数中返回9。

2
根据golang.org上的文档,Go语言中的defer语句可以安排一个函数调用(被延迟的函数)在执行defer语句所在的函数返回之前立即运行。
defer语句的参数首先会被求值,然后移动到defer栈中,在包含它的函数返回之前执行示例1: 语法1:
func display() (x int) {
    x = 10
    defer func() {
        fmt.Println(x)
    }()
    return 5
}

以上代码可以重写为,
语法 2:
func display() (x int) {
    x = 10
    defer func() {
        fmt.Println(x)
    }()
    x = 5
    // Deferred functions are executed.
    return
}

Result:

5

从封闭函数传递给匿名函数的变量是按引用传递的。当延迟执行函数被执行时,它会取出该内存位置中保存的值。

2
每当程序执行时,它会逐行执行。
当程序遇到defer语句时,它会将该defer语句推入堆栈并继续向前执行。
一旦程序结束,它就会从顶部开始执行堆栈中的那些defer语句。执行顺序是后进先出。
现在,理解基础知识。为了在程序末尾执行defer语句,它必须是可达代码,必须执行才能保存在堆栈中。如果程序在defer语句之前返回,则不会在程序末尾执行defer语句。 Playground
package main

import (
    "fmt"
)

func main() {
    defer fmt.Println("main's defer")
    a()
    b()
}

func a() {
    defer fmt.Println("a: defer before return")

    fmt.Println("a returns")

    return
}

func b() {
    defer fmt.Println("b: defer before return")

    fmt.Println("b returns")

    return

    defer fmt.Println("b: defer after return")
}

输出

a returns
a: defer before return
b returns
b: defer before return
main's defer

"b: defer after return" 没有被打印出来。

0

defer在结果参数设置完毕之后执行,但在outerFunction退出之前执行。

func deferLearning() {
    defer printStack()
    defer panicTest()
    fmt.Printf("result is 8: %d\n", changeNamedResultParameter())
    fmt.Printf("result is 7: %d\n", changeLocalVariable())
}

func printStack() {
    if e := recover(); e != nil {
        fmt.Println(string(debug.Stack()))
    }
}

func panicTest() {
    panic("panic in defer")
}

func changeNamedResultParameter() (x int) {
    defer func(n int) {
        fmt.Printf("defer parameter 0, %d\n", n)
        fmt.Printf("after the named result parameter is set, but before outerFunction exit: 7, %d\n", x)
        x++ //modify the named result parameter
    }(x)
    x = 7
    return x
}

func changeLocalVariable() int {
    var x int
    defer func(n int) {
        fmt.Printf("defer parameter 0, %d\n", n)
        fmt.Printf("get local variable x, after unnamed result parameter is set, but before outerFunction exit: 7, %d\n", x)
        x++ //modify the local variable
    }(x)
    x = 7
    return x
}

您将会看到调用 Goroutine 的堆栈跟踪:

runtime/debug.Stack()
    C:/Program Files/Go/src/runtime/debug/stack.go:24 +0x65
demo/learning.printStack()
    D:/go/demo/learning/learn_defer.go:17 +0x2a
panic({0x5f4d20, 0x669a90})
    C:/Program Files/Go/src/runtime/panic.go:838 +0x207
demo/learning.panicTest()
    D:/go/demo/learning/learn_defer.go:22 +0x27
demo/learning.deferLearning()
    D:/go/demo/learning/learn_defer.go:13 +0xef

-1
package main

import "fmt"

func deque(a *[]int) {
    *a = (*a)[1:]
}

func test_defer(a* []int) int{
    defer deque(a)

    return (*a)[0]
}

func main() {
    a := []int{1,2}
    fmt.Println(test_defer(&a))
    fmt.Println(a)
}

试试这段代码

go run test.go 
1
[2]

test_defer 返回 1,因此 defer 在 return 之后执行


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接