plainchant
  • Welcome PCT‘s Blog
  • Golang
    • golang基础
      • Go 语言 select 的实现原理
      • golang数字最大值
      • go-defer
      • Channel实现
      • go逃逸分析
      • Golang调度
  • Linux
  • Linux开发
    • 查看磁盘的UUID并挂载
    • Linux内核开发示例
    • 误删Linux内核后修复系统
    • linux补丁的创建和应用
    • Git常用命令
    • SystemV消息队列使用范例
    • ubuntu搭建全局代理
    • linux安装和配置
  • 嵌入式
    • 计算机为什么存在补码
    • 一种可靠串口协议
    • CRC校验算法
    • RasperryPi3 Ros系统安装(Debian)
  • 套接字编程
    • TCP建立连接过程分析
    • 线程的分发
    • TCP的并发处理epoll
    • TCP的并发处理select
    • 非阻塞TCP示例
    • 阻塞TCP示例
    • UDP组播
    • UDP广播
    • 非阻塞UDP示例(fcntl方式)
    • 非阻塞UDP示例
    • 局域网发现协议
    • socket通信机制浅析-前言
  • 交友网站
  • 区块链
    • Wasm虚拟机
      • wagon外部参数和内部参数的统一
      • Wagon实现log函数的第二种方法
      • Wagon实现一个log函数
      • go版本wasm解析器分析
      • 解析wasm二进制文件
      • ONT实现API的流程
      • Wasm工具安装使用
    • BCH
      • SLP代币协议
    • Cosmos
      • 区块链共识进化史
      • Tendermint 的区块构成
      • CoinEx 链 Gas 费指南
      • CoinEx交易类型收集
      • Cosmos简介和环境搭建
    • ETH
      • Geth命令详解
    • BTC
      • 助记词到地址
  • 算法
    • 动态规划
  • HTTP
    • URL 在浏览器被被输入到页面展现的过程中发生了什么
  • 运维后台
    • Docker学习笔记
  • 数据型应用系统设计
    • 数据密集型应用系统设计读书笔记
    • 数据编码与演化
      • Kafka配置
      • protobuf简介
    • MySQL
      • mysql安装和数据目录变更
      • 深入理解事务
      • MySQL事务问题验证
    • Redis
      • Redis缓存实现
      • Redis基本概念
由 GitBook 提供支持
在本页
  • 源码
  • 预计算参数
  • 编译
  • defer执行

这有帮助吗?

  1. Golang
  2. golang基础

go-defer

[TOC]

源码

runtime/runtime2.go defer数据结构:

// A _defer holds an entry on the list of deferred calls.
// If you add a field here, add code to clear it in freedefer.
type _defer struct {
   siz     int32 //是参数和结果的内存大小;
   started bool
   sp      uintptr // sp at time of defer
   pc      uintptr //sp 和 pc 分别代表栈指针和调用方的程序计数器;
   fn      *funcval //fn 是 defer 关键字中传入的函数;
   _panic  *_panic // panic that is running defer //_panic 是触发延迟调用的结构体,可能为空;
   link    *_defer
}

可以看到defer是一个链表,定义多个defer会组织成一条链依次调用:

预计算参数

defer后面跟的是一个函数,如果是一个正常函数,根据golang值传递原理,在定义defer时值就会被拷贝,因此虽然函数是延时调用的,但是值却是实时赋值。 如果想实现值的延时赋值,则需要为defer传递一个闭包,闭包的原理就是一个函数指针,那么传递给defer的是指针拷贝,而不是函数拷贝,就能实现值的延时赋值。

编译

编译源码:

func (s *state) stmt(n *Node) {


    switch n.Op {


    case ODEFER:


        s.call(n.Left, callDefer)


    }


}

从这里看到,编译器把defer当做了函数调用,因此defer fmt.Println(1)本质上如同defer(fmt.Println(1)),这里就解释了预计算参数的问题,defer是函数,那么后面的函数就是参数了。

defer执行

defer是在return之前执行的。这个在 官方文档中是明确说明了的。要使用defer时不踩坑,最重要的一点就是要明白,return xxx这一条语句并不是一条原子指令! 函数返回的过程是这样的:先给返回值赋值,然后调用defer表达式,最后才是返回到调用函数中。 defer表达式可能会在设置函数返回值之后,在返回到调用函数之前,修改返回值,使最终的函数返回值与你想象的不一致。 其实使用defer时,用一个简单的转换规则改写一下,就不会迷糊了。改写规则是将return语句拆成两句写,return xxx会被改写成:

返回值 = xxx
调用defer函数
空的return
上一页golang数字最大值下一页Channel实现

最后更新于4年前

这有帮助吗?