go defer

Created

2024-10-28 13:21:14

Updated

2024-10-28 13:21:20

Tip

常用于关闭资源,先进后执行

1 例子分析

1.1 例子1

func TestDefer(t *testing.T) {
    fmt.Println(111)
    // 先进后执行
    defer func() {
        fmt.Println(333)
    }()
    {
        // 代码块中的defer 也是在函数返回前执行
        defer func() {
            fmt.Println("代码块中的defer")
        }()
        fmt.Println("代码块执行完毕")
    }

    defer func() {
        fmt.Println(444)
    }()
    fmt.Println(222)
}
执行结果
111
代码块执行完毕
222
444
代码块中的defer
333

1.2 例子2

OS go version
Ubuntu 20.04.3 LTS go1.20.4
package main

func defer1() int {
    i := 2
    defer func() {
        i = 3
    }()
    return i
}
func main() {
    ret := defer1()
    // 结果是2
    println(ret)
}
package main

func defer1() (i int) {
    i = 2
    defer func() {
        i = 3
    }()
    return i
}
func main() {
    ret := defer1()
    // 结果是3
    println(ret)
}
go tool compile -S -N -l main.go

汇编代码使用的是go 1.15.4生成的, 省略部分.

"".defer1 STEXT size=155 args=0x8 locals=0x68
    0x0000 00000 (main.go:3)    TEXT    "".defer1(SB), ABIInternal, $104-8
    0x0013 00019 (main.go:3)    SUBQ    $104, SP
    0x0017 00023 (main.go:3)    MOVQ    BP, 96(SP)
    0x001c 00028 (main.go:3)    LEAQ    96(SP), BP
    # 返回值 r0
    0x0021 00033 (main.go:3)    MOVQ    $0, "".~r0+112(SP)
    # i
    0x002a 00042 (main.go:4)    MOVQ    $2, "".i+8(SP)
    0x0033 00051 (main.go:5)    MOVL    $8, ""..autotmp_3+16(SP)
    0x003b 00059 (main.go:5)    LEAQ    "".defer1.func1·f(SB), AX
    0x0042 00066 (main.go:5)    MOVQ    AX, ""..autotmp_3+40(SP)
    0x0047 00071 (main.go:5)    LEAQ    "".i+8(SP), AX
    0x004c 00076 (main.go:5)    MOVQ    AX, ""..autotmp_3+88(SP)
    0x0051 00081 (main.go:5)    LEAQ    ""..autotmp_3+16(SP), AX
    0x0056 00086 (main.go:5)    MOVQ    AX, (SP)
    0x005a 00090 (main.go:5)    CALL    runtime.deferprocStack(SB)
    0x005f 00095 (main.go:5)    NOP
    0x0060 00096 (main.go:5)    TESTL   AX, AX
    0x0062 00098 (main.go:5)    JNE 129
    0x0064 00100 (main.go:5)    JMP 102
    0x0066 00102 (main.go:9)    MOVQ    "".i+8(SP), AX
    # i 附给 返回值
    0x006b 00107 (main.go:9)    MOVQ    AX, "".~r0+112(SP)
    0x0070 00112 (main.go:9)    XCHGL   AX, AX
    0x0071 00113 (main.go:9)    CALL    runtime.deferreturn(SB)
"".defer1.func1 STEXT nosplit size=13 args=0x8 locals=0x0
    0x0000 00000 (main.go:5)    TEXT    "".defer1.func1(SB), NOSPLIT|ABIInternal, $0-8
    # i的地址 给AX
    0x0000 00000 (main.go:6)    MOVQ    "".&i+8(SP), AX
    # 赋值3 给 i
    0x0005 00005 (main.go:6)    MOVQ    $3, (AX)
"".main STEXT size=98 args=0x0 locals=0x18
    0x0000 00000 (main.go:11)   TEXT    "".main(SB), ABIInternal, $24-0
    0x000f 00015 (main.go:11)   SUBQ    $24, SP
    0x0013 00019 (main.go:11)   MOVQ    BP, 16(SP)
    0x0018 00024 (main.go:11)   LEAQ    16(SP), BP
    0x0020 00032 (main.go:12)   CALL    "".defer1(SB)
    # (SP) 就是 r0
    0x0025 00037 (main.go:12)   MOVQ    (SP), AX
    # 赋值给 ret
    0x0029 00041 (main.go:12)   MOVQ    AX, "".ret+8(SP)
    0x002e 00046 (main.go:13)   CALL    runtime.printlock(SB)
    0x0033 00051 (main.go:13)   MOVQ    "".ret+8(SP), AX
    0x0038 00056 (main.go:13)   MOVQ    AX, (SP)

"".defer1 STEXT size=140 args=0x8 locals=0x60
    0x0000 00000 (main.go:3)    TEXT    "".defer1(SB), ABIInternal, $96-8
    0x000f 00015 (main.go:3)    SUBQ    $96, SP
    0x0013 00019 (main.go:3)    MOVQ    BP, 88(SP)
    0x0018 00024 (main.go:3)    LEAQ    88(SP), BP
    # i 赋值
    0x001d 00029 (main.go:3)    MOVQ    $0, "".i+104(SP)
    0x0026 00038 (main.go:4)    MOVQ    $2, "".i+104(SP)
    0x002f 00047 (main.go:5)    MOVL    $8, ""..autotmp_2+8(SP)
    0x0037 00055 (main.go:5)    LEAQ    "".defer1.func1·f(SB), AX
    0x003e 00062 (main.go:5)    MOVQ    AX, ""..autotmp_2+32(SP)
    0x0043 00067 (main.go:5)    LEAQ    "".i+104(SP), AX
    0x0048 00072 (main.go:5)    MOVQ    AX, ""..autotmp_2+80(SP)
    0x004d 00077 (main.go:5)    LEAQ    ""..autotmp_2+8(SP), AX
    0x0052 00082 (main.go:5)    MOVQ    AX, (SP)
    0x0056 00086 (main.go:5)    PCDATA  $1, $0
    0x0056 00086 (main.go:5)    CALL    runtime.deferprocStack(SB)
    0x005b 00091 (main.go:5)    TESTL   AX, AX
    0x005d 00093 (main.go:5)    JNE 114
    0x005f 00095 (main.go:5)    NOP
    0x0060 00096 (main.go:5)    JMP 98
    0x0062 00098 (main.go:9)    XCHGL   AX, AX
    0x0063 00099 (main.go:9)    CALL    runtime.deferreturn(SB)
"".defer1.func1 STEXT nosplit size=13 args=0x8 locals=0x0
    0x0000 00000 (main.go:5)    TEXT    "".defer1.func1(SB), NOSPLIT|ABIInternal, $0-8
    # i的地址 给AX
    0x0000 00000 (main.go:6)    MOVQ    "".&i+8(SP), AX
    # 赋值3 给 i
    0x0005 00005 (main.go:6)    MOVQ    $3, (AX)
"".main STEXT size=98 args=0x0 locals=0x18
    0x0000 00000 (main.go:11)   TEXT    "".main(SB), ABIInternal, $24-0
    0x000f 00015 (main.go:11)   SUBQ    $24, SP
    0x0013 00019 (main.go:11)   MOVQ    BP, 16(SP)
    0x0018 00024 (main.go:11)   LEAQ    16(SP), BP
    0x0020 00032 (main.go:12)   CALL    "".defer1(SB)
    # (SP) 就是 返回值也是 i
    0x0025 00037 (main.go:12)   MOVQ    (SP), AX
    # 赋值给 ret
    0x0029 00041 (main.go:12)   MOVQ    AX, "".ret+8(SP)
    0x002e 00046 (main.go:13)   CALL    runtime.printlock(SB)
    0x0033 00051 (main.go:13)   MOVQ    "".ret+8(SP), AX
    0x0038 00056 (main.go:13)   MOVQ    AX, (SP)

先设置返回值,然后执行defer ,最后将返回值复制给ret

有名返回值的情况是 返回值与i变量是同一个内存位置, defer会修改这个i,就是修改了返回值,最后复制给ret,这样结果就是3了

匿名返回值的情况是 会有一个临时变量(一块在main栈帧中的内存作为返回值),先设置返回值=2,接着执行defer,修改的是defer1函数栈帧中的 i变量,对返回值无影响

1.3 例子3

package main

func defer1() (i int) {
    i = 2
    defer func() {
        // 之前例子的汇编可以看到,defer 里会用到 &i
        // 实际上变成一个闭包了.
        // 是1
        println("defer1", i)
        i = 3
    }()
    // 设置返回值, i就是, 所以i=1
    return 1
}
func main() {
    // 3
    println(defer1())
}

1.4 例子4

package main

func add(i *int) int {
    println(*i)
    *i = *i + 1
    return *i
}

func main() {
    var i int = 1
    // 会执行它的参数 i变成2
    defer println("defer:", add(&i))
    defer func() {
        // 闭包了
        // 使用了外部变量 打印5
        println(i)
    }()
    // 2
    println("before return: ", i)
    i = 5
    println("return")
}

2 底层原理

Back to top