分享
  1. 首页
  2. 文章

Golang defer 使用时的坑

shenlanzifa · · 4032 次点击 · · 开始浏览
这是一个创建于 的文章,其中的信息可能已经有所发展或是发生改变。

defer是golang语言中的关键字,用于资源的释放,会在函数返回之前进行调用。一般采用如下模式:

f,err := os.Open(filename)
if err != nil {
 panic(err)
}
defer f.Close()

如果有多个defer表达式,调用顺序类似于栈,越后面的defer表达式越先被调用。

不过如果对defer的了解不够深入,使用起来可能会踩到一些坑,尤其是跟带命名的返回参数一起使用时。在讲解defer的实现之前先看一看使用defer容易遇到的问题。


先来看看几个例子。例1:

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

例2:

func f() (r int) {
 t := 5
 defer func() {
 t = t + 5
 }()
 return t
}

例3:

func f() (r int) {
 defer func(r int) {
 r = r + 5
 }(r)
 return 1
}

请读者先不要运行代码,在心里跑一遍结果,然后去验证。

例1的正确答案不是0,例2的正确答案不是10,如果例3的正确答案不是6......

defer是在return之前执行的。这个在 官方文档中是明确说明了的。要使用defer时不踩坑,最重要的一点就是要明白,return xxx这一条语句并不是一条原子指令!

函数返回的过程是这样的:先给返回值赋值,然后调用defer表达式,最后才是返回到调用函数中。

defer表达式可能会在设置函数返回值之后,在返回到调用函数之前,修改返回值,使最终的函数返回值与你想象的不一致。

其实使用defer时,用一个简单的转换规则改写一下,就不会迷糊了。改写规则是将return语句拆成两句写,return xxx会被改写成:

返回值 = xxx
调用defer函数
空的return

先看例1,它可以改写成这样:

func f() (result int) {
 result = 0 //return语句不是一条原子调用,return xxx其实是赋值+ret指令
 func() { //defer被插入到return之前执行,也就是赋返回值和ret指令之间
 result++
 }()
 return
}

所以这个返回值是1。

再看例2,它可以改写成这样:

func f() (r int) {
 t := 5
 r = t //赋值指令
 func() { //defer被插入到赋值与返回之间执行,这个例子中返回值r没被修改过
 t = t + 5
 }
 return //空的return指令
}

所以这个的结果是5。

最后看例3,它改写后变成:

func f() (r int) {
 r = 1 //给返回值赋值
 func(r int) { //这里改的r是传值传进去的r,不会改变要返回的那个r值
 r = r + 5
 }(r)
 return //空的return
}

所以这个例子的结果是1。

defer确实是在return之前调用的。但表现形式上却可能不像。本质原因是return xxx语句并不是一条原子指令,defer被插入到了赋值 与 ret之间,因此可能有机会改变最终的返回值。

defer的实现

defer关键字的实现跟go关键字很类似,不同的是它调用的是runtime.deferproc而不是runtime.newproc。

在defer出现的地方,插入了指令call runtime.deferproc,然后在函数返回之前的地方,插入指令call runtime.deferreturn。

普通的函数返回时,汇编代码类似:

add xx SP
return

如果其中包含了defer语句,则汇编代码是:

call runtime.deferreturn,
add xx SP
return

goroutine的控制结构中,有一张表记录defer,调用runtime.deferproc时会将需要defer的表达式记录在表中,而在调用runtime.deferreturn的时候,则会依次从defer表中出栈并执行。


有疑问加站长微信联系(非本文作者)

本文来自:CSDN博客

感谢作者:shenlanzifa

查看原文:Golang defer 使用时的坑

入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:692541889

关注微信
4032 次点击 ∙ 1 赞
1 回复 | 直到 2025年05月15日 09:05:16
暂无回复
添加一条新回复 (您需要 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
  • 图片支持拖拽、截图粘贴等方式上传

用户登录

没有账号?注册
(追記) (追記ここまで)

今日阅读排行

    加载中
(追記) (追記ここまで)

一周阅读排行

    加载中

关注我

  • 扫码关注领全套学习资料 关注微信公众号
  • 加入 QQ 群:
    • 192706294(已满)
    • 731990104(已满)
    • 798786647(已满)
    • 729884609(已满)
    • 977810755(已满)
    • 815126783(已满)
    • 812540095(已满)
    • 1006366459(已满)
    • 692541889

  • 关注微信公众号
  • 加入微信群:liuxiaoyan-s,备注入群
  • 也欢迎加入知识星球 Go粉丝们(免费)

给该专栏投稿 写篇新文章

每篇文章有总共有 5 次投稿机会

收入到我管理的专栏 新建专栏