关于defer行为的Go语言面试题

Go language interview question about defer behavior

我有 2 个代码示例:

func test() int {
    var x int
    defer func() {

        x++
    }()
    x = 1
    return x
}

func main() {
    fmt.Println(test())
}

它returns:1。但是,以下代码示例的行为有所不同:

func test() (x int) {
    defer func() {

        x++
    }()
    x = 1
    return
}

func main() {
    fmt.Println(test())
}

它returns 2.


为了搞清楚是怎么回事,我反汇编了代码

第一个代码片段(输出为1):

  main.go:19            0x48cf40                64488b0c25f8ffffff      MOVQ FS:0xfffffff8, CX
  main.go:19            0x48cf49                483b6110                CMPQ 0x10(CX), SP
  main.go:19            0x48cf4d                7678                    JBE 0x48cfc7
  main.go:19            0x48cf4f                4883ec58                SUBQ [=12=]x58, SP
  main.go:19            0x48cf53                48896c2450              MOVQ BP, 0x50(SP)
  main.go:19            0x48cf58                488d6c2450              LEAQ 0x50(SP), BP
  main.go:20            0x48cf5d                e83effffff              CALL main.test(SB)
  main.go:20            0x48cf62                e8c9bff7ff              CALL runtime.convT64(SB)
  main.go:20            0x48cf67                488b442408              MOVQ 0x8(SP), AX
  main.go:20            0x48cf6c                0f57c0                  XORPS X0, X0
  main.go:20            0x48cf6f                0f11442440              MOVUPS X0, 0x40(SP)
  main.go:20            0x48cf74                488d0d05090100          LEAQ 0x10905(IP), CX
  main.go:20            0x48cf7b                48894c2440              MOVQ CX, 0x40(SP)
  main.go:20            0x48cf80                4889442448              MOVQ AX, 0x48(SP)
  print.go:274          0x48cf85                488b0524020d00          MOVQ os.Stdout(SB), AX
  print.go:274          0x48cf8c                488d0d0dd70400          LEAQ go.itab.*os.File,io.Writer(SB), CX
  print.go:274          0x48cf93                48890c24                MOVQ CX, 0(SP)
  print.go:274          0x48cf97                4889442408              MOVQ AX, 0x8(SP)
  print.go:274          0x48cf9c                488d442440              LEAQ 0x40(SP), AX
  print.go:274          0x48cfa1                4889442410              MOVQ AX, 0x10(SP)
  print.go:274          0x48cfa6                48c744241801000000      MOVQ [=12=]x1, 0x18(SP)
  print.go:274          0x48cfaf                48c744242001000000      MOVQ [=12=]x1, 0x20(SP)
  print.go:274          0x48cfb8                e84397ffff              CALL fmt.Fprintln(SB)
  print.go:274          0x48cfbd                488b6c2450              MOVQ 0x50(SP), BP
  print.go:274          0x48cfc2                4883c458                ADDQ [=12=]x58, SP
  print.go:274          0x48cfc6                c3                      RET
  main.go:19            0x48cfc7                e8d447fcff              CALL runtime.morestack_noctxt(SB)
  main.go:19            0x48cfcc                e96fffffff              JMP main.main(SB)

对于第二个代码片段(其输出为2):

  main.go:18            0x48cf30                64488b0c25f8ffffff      MOVQ FS:0xfffffff8, CX
  main.go:18            0x48cf39                483b6110                CMPQ 0x10(CX), SP
  main.go:18            0x48cf3d                7678                    JBE 0x48cfb7
  main.go:18            0x48cf3f                4883ec58                SUBQ [=13=]x58, SP
  main.go:18            0x48cf43                48896c2450              MOVQ BP, 0x50(SP)
  main.go:18            0x48cf48                488d6c2450              LEAQ 0x50(SP), BP
  main.go:19            0x48cf4d                e84effffff              CALL main.test(SB)
  main.go:19            0x48cf52                e8d9bff7ff              CALL runtime.convT64(SB)
  main.go:19            0x48cf57                488b442408              MOVQ 0x8(SP), AX
  main.go:19            0x48cf5c                0f57c0                  XORPS X0, X0
  main.go:19            0x48cf5f                0f11442440              MOVUPS X0, 0x40(SP)
  main.go:19            0x48cf64                488d0d15090100          LEAQ 0x10915(IP), CX
  main.go:19            0x48cf6b                48894c2440              MOVQ CX, 0x40(SP)
  main.go:19            0x48cf70                4889442448              MOVQ AX, 0x48(SP)
  print.go:274          0x48cf75                488b0534020d00          MOVQ os.Stdout(SB), AX
  print.go:274          0x48cf7c                488d0d1dd70400          LEAQ go.itab.*os.File,io.Writer(SB), CX
  print.go:274          0x48cf83                48890c24                MOVQ CX, 0(SP)
  print.go:274          0x48cf87                4889442408              MOVQ AX, 0x8(SP)
  print.go:274          0x48cf8c                488d442440              LEAQ 0x40(SP), AX
  print.go:274          0x48cf91                4889442410              MOVQ AX, 0x10(SP)
  print.go:274          0x48cf96                48c744241801000000      MOVQ [=13=]x1, 0x18(SP)
  print.go:274          0x48cf9f                48c744242001000000      MOVQ [=13=]x1, 0x20(SP)
  print.go:274          0x48cfa8                e85397ffff              CALL fmt.Fprintln(SB)
  print.go:274          0x48cfad                488b6c2450              MOVQ 0x50(SP), BP
  print.go:274          0x48cfb2                4883c458                ADDQ [=13=]x58, SP
  print.go:274          0x48cfb6                c3                      RET
  main.go:18            0x48cfb7                e8e447fcff              CALL runtime.morestack_noctxt(SB)
  main.go:18            0x48cfbc                e96fffffff              JMP main.main(SB)

Golang 规范说 this 关于 defer 语句:

[...] if the deferred function is a function literal and the surrounding function has named result parameters that are in scope within the literal, the deferred function may access and modify the result parameters before they are returned.

(我的重点)

your first snippet中,函数test没有命名的return参数; x 只是一个局部变量。因此,您的 defer 语句不能修改函数 test.

的结果

your second snippet 中,函数 test 有一个命名的 return 参数 x,它在您的函数文字范围内。因此,defer 语句可以(并且确实)修改函数 test.

的结果