一句话结论

defer 在函数返回前执行,遵循后进先出(LIFO)栈序。defer 的参数在声明时求值(不是执行时),与 return 的交互由命名返回值决定。

核心原理

执行顺序:LIFO

func demo() {
    defer fmt.Println("1")
    defer fmt.Println("2")
    defer fmt.Println("3")
}
// 输出: 3 2 1

参数求值时机

func demo() {
    x := 1
    defer fmt.Println(x)  // 此时 x=1 已被"快照"
    x = 2
    // defer 执行时输出 1,不是 2
}

defer + return + 命名返回值(经典题)

// 题 1:命名返回值 + defer 修改返回值
func f1() (result int) {
    defer func() { result++ }()
    return 0  // output: 1
}
// 执行顺序: result=0 → defer result++ → return result

// 题 2:非命名返回值
func f2() int {
    result := 0
    defer func() { result++ }()
    return result  // output: 0
}
// defer 改的是局部变量 result,不改变返回值

// 题 3:defer 修改指针指向的内容
func f3() (r *int) {
    x := 0
    defer func() { x++ }()
    r = &x
    return  // output: &1
}

铁律: return 不是原子操作。分两步:① 给返回值赋值 ② 执行 defer ③ 真正返回。defer 可以修改命名返回值(在步骤 ① 和 ③ 之间执行)。

执行流程(return 的三步走)

return x
  → 1. result = x      (给返回值赋值)
  → 2. 执行所有 defer  (LIFO 顺序,可修改命名返回值)
  → 3. RET 指令        (真正返回)

项目中的应用

在 项目二-物联网AI-Agent中枢控制平台 中,用 defer 保证锁释放和资源清理:

func (h *DeviceHub) UpdateDevice(id string, status *DeviceStatus) {
    h.mu.Lock()
    defer h.mu.Unlock()  // 无论函数怎么退出都释放锁

    h.statusMap[id] = status

    f, err := os.Create("device.log")
    if err != nil {
        return  // Lock 仍然会被释放
    }
    defer f.Close()  // 后打开的先关闭
}

异常与边界

defer + recover

func safeCall() {
    defer func() {
        if r := recover(); r != nil {
            log.Printf("recovered: %v", r)
        }
    }()
    panic("something wrong")
}
// 程序不会崩溃

defer 在循环中

// ❌ 坏:文件打开 1000 次但只在函数退出时关闭最后一个
func bad() {
    for _, file := range files {
        f, _ := os.Open(file)
        defer f.Close()  // 累积累积,内存泄漏!
    }
}

// ✅ 好:匿名函数包装,每次迭代后关闭
func good() {
    for _, file := range files {
        func() {
            f, _ := os.Open(file)
            defer f.Close()
            // 使用 f
        }()
    }
}

defer 的性能

// Go 1.14+ 优化后,defer 在常见路径上几乎零开销
// 但 panic 路径仍有开销(需要扫描 defer 链)

高频面试问题

Q: defer 的执行顺序是什么?

30 秒回答: 后进先出(LIFO)——最后一个 defer 最先执行。像函数调用栈一样工作。

Q: defer 可以修改返回值吗?

30 秒回答: 只有当返回值是命名返回值时才能修改。func f() (x int) { defer func() { x++ }(); return 0 } 返回 1。

最小实验

func main() {
    fmt.Println("命名返回值修改:", named())       // 1
    fmt.Println("非命名返回值:", unnamed())       // 0
}

func named() (result int) {
    defer func() { result++ }()
    return 0
}

func unnamed() int {
    result := 0
    defer func() { result++ }()
    return result
}

速记

defer = LIFO。参数声明时求值。return 分三步(赋值→defer→返回)。命名返回值可被 defer 修改。循环中 defer 用匿名函数包裹。defer+recover 捕获 panic。