当前位置: 首页 > news >正文

GO闭包实现原理(汇编级讲解)

go语言闭包实现原理(汇编层解析)

1.起因

今天开始学习go语言,在学到go闭包时候,原本以为go闭包的实现方式就是类似于如下cpp lambda

  1. value通过值传递,mutable修饰可以让value可以修改,但是地址不可能一样
  2. value通过引用传递,但是在其他地方调用时,这个value局部变量早就释放,会访问到脏数据
std::function<void()> func(){int value = 666;return [value]()mutable{value++;std::cout << &value;}
}

但是经过测试,我居然惊奇的发现在go的fun函数闭包value变量的地址一模一样,但是在c++的理解中这是不可能的(c++中栈随着函数退出而销毁,value也成为脏数据,一旦访问很可能会读到意料之外的数据)

image-20231130212413187

image-20231130212421755

image-20231130212432275

2.探索

于是,我查看了go编译后的汇编代码,首先先看看闭包经典代码,再看删除第六行不在闭包内修改变量value的变化的汇编代码

1.闭包经典代码

image-20231130221210920

image-20231130221309176

首先,我们发现不一样的是 var value int = 100 会调用 runtime.newobject 函数(内置new函数的底层函数,它返回数据类型指针)。在正常函数局部变量的定义时,例如下:

2.删除第六行代码,不在闭包中修改value

image-20231130221400666

我们能发现 var value int = 100 是不会调用 runtime.newobject 函数的,它对应的汇编是如下

image-20231130223626467

对比两段代码的汇编,我们可以看见一个数据结构:闭包对象数据结构

type noalg struct{F unitptr	//函数对象X0 *int		//第一段代码的汇编,在闭包内修改对象//X0 int	//第二段代码的汇编,不在闭包内修改对象
}

之后,在通过 runtime.newobject 函数创建了闭包对象。而且由于 LEAQ xxx yyy代表的是将 xxx 指针,传递给 yyy,因此 outer 函数最终的返回,其实是闭包结构体对象指针。很明显,闭包对象会被分配至堆上,变量x也会随着对象逃逸至堆。这就很好地解释了为什么x变量没有随着函数栈的销毁而消亡。

验证

通过go build指令的逃逸分析,可以看见,第一段代码的变量value和函数对象都分配到了堆上面

这其实就是Go编译器做得精妙的地方:当闭包内没有对外部变量造成修改时,Go 编译器会将自由变量的引用传递优化为直接值传递,避免变量逃逸。

PS D:\goProject\src\learn> go build -gcflags '-m -m -l' main.go
# command-line-arguments
./main.go:4:6: fun capturing by ref: value (addr=false assign=true width=8)
./main.go:7:13: value escapes to heap:
./main.go:7:13:   flow: {storage for ... argument} = &{storage for value}:
./main.go:7:13:     from value (spill) at ./main.go:7:13
./main.go:7:13:     from ... argument (slice-literal-element) at ./main.go:7:12
./main.go:7:13:   flow: {heap} = {storage for ... argument}:
./main.go:7:13:     from ... argument (spill) at ./main.go:7:12
./main.go:7:13:     from fmt.Print(... argument...) (call parameter) at ./main.go:7:12
./main.go:5:9: func literal escapes to heap:
./main.go:5:9:   flow: ~r0 = &{storage for func literal}:
./main.go:5:9:     from func literal (spill) at ./main.go:5:9
./main.go:5:9:     from return func literal (return) at ./main.go:5:2
./main.go:4:6: value escapes to heap:
./main.go:4:6:   flow: {storage for func literal} = &value:
./main.go:4:6:     from value (captured by a closure) at ./main.go:6:3
./main.go:4:6:     from value (reference) at ./main.go:6:3
./main.go:4:6: moved to heap: value				//变量value逃逸
./main.go:5:9: func literal escapes to heap		//函数逃逸
./main.go:7:12: ... argument does not escape
./main.go:7:13: value escapes to heap

总结

函数闭包一点也不神秘,它就是函数和引用环境而组合的实体。在Go中,闭包在底层是一个结构体对象,它包含了函数指针与自由变量。

Go编译器的逃逸分析机制,会将闭包对象分配至堆中,这样自由变量就不会随着函数栈的销毁而消失,它能依附着闭包实体而一直存在。因此,闭包使用的优缺点是很明显的:闭包能够避免使用全局变量,转而维持自由变量长期存储在内存之中;但是,这种隐式地持有自由变量,在使用不当时,会很容易造成内存浪费与泄露。
附着闭包实体而一直存在。因此,闭包使用的优缺点是很明显的:闭包能够避免使用全局变量,转而维持自由变量长期存储在内存之中;但是,这种隐式地持有自由变量,在使用不当时,会很容易造成内存浪费与泄露。

相关文章:

  • Python查找列表中不重复的数字
  • 【ArcGIS微课1000例】0080:ArcGIS将shp转json(geojson)案例教程
  • 网络基础(八):路由器的基本原理及配置
  • Redis系列之简单实现watchDog自动续期机制
  • android项目实战之编辑器图片上传预览
  • 使用python脚本一个简单的搭建ansible集群
  • 数据在网络中是怎么传输的?
  • 数据库结构
  • WT588F02B-8S语音芯片在水波炉中的应用:提升用户体验与安全性
  • GPT-4V 在机器人领域的应用
  • Vue中比较两个JSON对象的差异
  • 『npm』一条命令快速配置npm淘宝国内镜像
  • Dockerfile创建镜像INMP+wordpress
  • 优雅玩转实验室服务器(三)vscode is all you need
  • 波奇学Linux:Linux进程状态,进程优先级
  • SegmentFault for Android 3.0 发布
  • 【跃迁之路】【699天】程序员高效学习方法论探索系列(实验阶段456-2019.1.19)...
  • 2017 前端面试准备 - 收藏集 - 掘金
  • 2017-08-04 前端日报
  • Android框架之Volley
  • Angular 响应式表单 基础例子
  • canvas 五子棋游戏
  • electron原来这么简单----打包你的react、VUE桌面应用程序
  • Java,console输出实时的转向GUI textbox
  • javascript从右向左截取指定位数字符的3种方法
  • JavaScript实现分页效果
  • Java到底能干嘛?
  • nodejs:开发并发布一个nodejs包
  • Python利用正则抓取网页内容保存到本地
  • Spring声明式事务管理之一:五大属性分析
  • yii2权限控制rbac之rule详细讲解
  • 安装python包到指定虚拟环境
  • 从@property说起(二)当我们写下@property (nonatomic, weak) id obj时,我们究竟写了什么...
  • 从tcpdump抓包看TCP/IP协议
  • 讲清楚之javascript作用域
  • 力扣(LeetCode)22
  • 试着探索高并发下的系统架构面貌
  • 体验javascript之美-第五课 匿名函数自执行和闭包是一回事儿吗?
  • 正则与JS中的正则
  • 最近的计划
  • 1.Ext JS 建立web开发工程
  • #includecmath
  • $NOIp2018$劝退记
  • (delphi11最新学习资料) Object Pascal 学习笔记---第5章第5节(delphi中的指针)
  • (笔试题)合法字符串
  • (附源码)php新闻发布平台 毕业设计 141646
  • (四)搭建容器云管理平台笔记—安装ETCD(不使用证书)
  • (四)七种元启发算法(DBO、LO、SWO、COA、LSO、KOA、GRO)求解无人机路径规划MATLAB
  • (五)IO流之ByteArrayInput/OutputStream
  • (转)eclipse内存溢出设置 -Xms212m -Xmx804m -XX:PermSize=250M -XX:MaxPermSize=356m
  • (转载)(官方)UE4--图像编程----着色器开发
  • .NET 5种线程安全集合
  • .NET Core 中插件式开发实现
  • .NET 中小心嵌套等待的 Task,它可能会耗尽你线程池的现有资源,出现类似死锁的情况
  • /etc/fstab 只读无法修改的解决办法