3.4 defer关键字

defer和go一样都是Go语言提供的关键字。defer用于资源的释放,会在函数返回之前进行调用。一般采用如下模式:

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

如果有多个defer表达式,调用顺序类似于栈,越后面的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表中出栈并执行。

golang中defer的正确使用方式(源自深入解析go)的更多相关文章

  1. 学术Essay写作中Introduction的正确打开方式

    其实在学术essay写作过程中,很多留学生经常不知道如何写introduction,所以有些开头的模板句就出现了,比如,With the development of society/With the ...

  2. 延宕执行,妙用无穷,Go lang1.18入门精炼教程,由白丁入鸿儒,Golang中defer关键字延迟调用机制使用EP17

    先行定义,延后执行.不得不佩服Go lang设计者天才的设计,事实上,defer关键字就相当于Python中的try{ ...}except{ ...}finally{...}结构设计中的finall ...

  3. golang中defer的使用规则

    转自个人博客chinazt.cc 在golang当中,defer代码块会在函数调用链表中增加一个函数调用.这个函数调用不是普通的函数调用,而是会在函数正常返回,也就是return之后添加一个函数调用. ...

  4. [转]golang中defer的使用规则

    转载于:https://studygolang.com/articles/10167 在golang当中,defer代码块会在函数调用链表中增加一个函数调用.这个函数调用不是普通的函数调用,而是会在函 ...

  5. golang中defer的理解

    在golang当中,defer代码块会在函数调用链表中增加一个函数调用.这个函数调用不是普通的函数调用,而是会在函数正常返回,也就是return之后添加一个函数调用.因此,defer通常用来释放函数内 ...

  6. Golang中defer、return、返回值之间执行顺序的坑

    原文链接:https://studygolang.com/articles/4809 Go语言中延迟函数defer充当着 cry...catch 的重任,使用起来也非常简便,然而在实际应用中,很多go ...

  7. 【GoLang】golang 中 defer 参数的蹊跷

    参考资料: http://studygolang.com/articles/7994--Defer函数调用参数的求值 golang的闭包和普通函数调用区别:http://studygolang.com ...

  8. golang中defer的详解 转自https://blog.csdn.net/skh2015java/article/details/77081250

    Go里的defer很有用,尤其在很多执行模块化操作时,初始化时给各个需要执行的模块传入参数,但是这些参数有些事在模块执行过程中才赋值的. 这时候有了defer就不会把代码写的很凌乱. Go的defer ...

  9. JavaScript中Array的正确使用方式

    在 JavaScript 中正确使用地使用 Array 的方法如下: 用 Array.includes 代替 Array.indexOf “如果你要在数组中查找元素,请使用 Array.indexOf ...

随机推荐

  1. C#程序 -- 以管理员权限运行

    阅读目录 一.判断程序是否以管理员身份运行 C#程序以管理员权限运行 在Vista 和 Windows 7 及更新版本的操作系统,增加了 UAC(用户账户控制) 的安全机制,如果 UAC 被打开,用户 ...

  2. js--动画

    运动框架实现思想1.速度(改变值left,right,width,height,opacity)2.缓冲运动3.多物体运动4.任意值变化5.链式运动6.同时运动 我们先来介绍第一章改变left值来使物 ...

  3. typename T::SubType * ptr;

    #include <iostream> using namespace std; template<class T> class MyClass{ public: typena ...

  4. 如何使用h5py读入数据

    在网上找到的方法,如下: import h5py import numpy as np #HDF5的写入: imgData = np.zeros((2,4)) f = h5py.File('HDF5_ ...

  5. 到底该不该用RTOS——rtos的优点

    我现在要不要学习RTOS? 学习RTOS有什么好处? 我的项目要不要跑RTOS? ······等等一些关于RTOS的问题,其实归根结底还是你对RTOS了解的不够,项目开发的经验还不足等. 针对这部分朋 ...

  6. abp radio表单元素 消失了

    框架将原本的元素都隐藏掉了,取而代之的是根据label定义了自己的样式,如下: [type="radio"]:not(:checked)+label {    padding-le ...

  7. VS 2015秘钥

    专业版:HMGNV-WCYXV-X7G9W-YCX63-B98R2企业版:HM6NR-QXX7C-DFW2Y-8B82K-WTYJV

  8. 数列的通项公式$a_n$的求法

    前言 求数列的通项公式,其本质是求函数的解析式.重点理解内涵. 求解必备 你见到这样的式子\(a_{n+1}-a_n = m\) (\(m\)常数)你一定会反应出是等差数列,那么见到 \(S_{n+1 ...

  9. APIO2019 游记

    \(\text {Cu}\)滚粗了,滚粗选手不配拥有游记.

  10. itop4412uboot中支持usbhub

    hub采用3503a,3.3v regulator使用vbat供电,1.2的regulator使用1.8v供电,reset开始是拉高的,而3503的工作流程首先要reset,即引脚先拉低,再释放,造成 ...