defer

1.注意事项和细节

1.1 调用顺序

代码输出结果是什么?

func main() {
    for i := 0; i < 10; i++ {
        defer fmt.Println(i)
    }
}
9
8
7
6
5
4
3
2
1
0
多个 defer 语句和栈一样,即"先进后出"特性,越后面的 defer 表达式越先被调用。

1.2 defer 拷贝

代码输出结果是什么?

func main() {
    fmt.Println(Sum(1, 2))
}

func Sum(num1, num2 int) int {
    defer fmt.Println("num1:", num1)
    defer fmt.Println("num2:", num2)
    num1++
    num2++
    return num1 + num2
}
1
2
正确的答案是 num1 为 1,num2 为 2,这两个变量并不受 num1++、num2++的影响,因为 defer 将语句放入到栈中时,也会将相关的值拷贝同时入栈。

1.3 defer 与 return 的返回时机


return的返回步骤
函数在返回时,首先函数返回时会自动创建一个返回变量假设为 ret (如果是命名返回值的函数则不会创建),函数返回时要将变量 i 赋值给 ret,即有 ret = i;
然后检查函数中是否有 defer 存在,若有则执行 defer 中部分;
最后返回 ret;

匿名返回值函数

func Anonymous() int {
    var i int
    defer func() {
        i++
        fmt.Println("defer2 value is ", i)
    }()

    defer func() {
        i++
        fmt.Println("defer1 in value is ", i)
    }()

    return i
}
0

命名返回值的函数

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

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

    return j
}
2

2.六大原则

  • defer 后面跟的必须是函数或者方法调用,defer 后面的表达式不能加括号;
  • defer 的函数或方法的参数的值在定义 defer 语句的时候就被确定下来了;
  • defer 的函数或者方法如果存在多级调用,只有最后一个函数或方法会被 defer 到函数 return 或者 panic 之前执行;
  • defer 的函数执行顺序满足 LIFO 原则,后 defer 的先执行;
  • defer 的函数可以对defer语句所在的函数的命名返回值做读取和修改操作;
  • 即使 defer 语句执行了,被 defer 的函数不一定会执行;

3.源码

// A _defer holds an entry on the list of deferred calls.
// If you add a field here, add code to clear it in deferProcStack.
// This struct must match the code in cmd/compile/internal/ssagen/ssa.go:deferstruct
// and cmd/compile/internal/ssagen/ssa.go:(*state).call.
// Some defers will be allocated on the stack and some on the heap.
// All defers are logically part of the stack, so write barriers to
// initialize them are not required. All defers must be manually scanned,
// and for heap defers, marked.
type _defer struct {
	started bool //标记是否开始执行defer
	heap    bool
	// openDefer indicates that this _defer is for a frame with open-coded
	// defers. We have only one defer record for the entire frame (which may
	// currently have 0, 1, or more defers active).
	openDefer bool
	sp        uintptr // sp at time of defer
	pc        uintptr // pc at time of defer
	fn        func()  // can be nil for open-coded defers
	_panic    *_panic // panic that is running defer,指向当前执行的panic
	link      *_defer // next defer on G; can point to either heap or stack!

	// If openDefer is true, the fields below record values about the stack
	// frame and associated function that has the open-coded defer(s). sp
	// above will be the sp for the frame, and pc will be address of the
	// deferreturn call in the function.
	fd   unsafe.Pointer // funcdata for the function associated with the frame
	varp uintptr        // value of varp for the stack frame
	// framepc is the current pc associated with the stack frame. Together,
	// with sp above (which is the sp associated with the stack frame),
	// framepc/sp can be used as pc/sp pair to continue a stack trace via
	// gentraceback().
	framepc uintptr
}
最后更新于