Go语言编译原理之变量捕获


Posted in Golang onAugust 05, 2022

前言

在前边的几篇文章中已经基本分享完了编译器前端的一些工作,后边的几篇主要是关于编译器对抽象语法树进行分析和重构,然后完成一系列的优化,其中包括以下五个部分:

  • 变量捕获
  • 函数内联
  • 逃逸分析
  • 闭包重写
  • 遍历函数

后边的五篇文章主要就是上边这五个主题,本文分享的是变量捕获,变量捕获主要是针对闭包场景的,因为闭包函数中可能引用闭包外的变量,因此变量捕获需要明确在闭包中通过值引用或地址引用的方式来捕获变量

变量捕获概述

下边通过一个示例来看一下什么是变量捕获

package main
import (
	"fmt"
)
func main() {
	a := 1
	b := 2
	go func() {
		//在闭包里对a或b进行了重新赋值,也会改变引用方式
		fmt.Println(a, b)
	}()
	a = 666
}

我们可以看到在闭包中引用了外部的变量a、b,由于变量a在闭包之后进行了其他赋值操作,因此在闭包中,a、b变量的引用方式会有所不同。在闭包中,必须采取地址引用的方式对变量a进行操作,而对变量b的引用将通过直接值传递的方式进行

我们可以通过如下方式查看当前程序闭包变量捕获的情况

go tool compile -m=2 main.go | grep capturing

Go语言编译原理之变量捕获

assign=true代表变量a在闭包完成后又进行了赋值操作

也可以看一个稍微复杂的

func adder() func(int) int {//累加器
	sum := 0 //地址引用
	return func(v int) int {
		sum += v
		return sum
	}
}
func main() {
	a := adder()
	for i:=0;i<10;i++{
		fmt.Printf("0 + 1 + ... + %d = %d\n", i, a(i))
	}
}

上一篇文章分享了类型检查,我们可以继续顺着编译的入口文件中类型检查后边的代码往下看,你会看到如下这段代码

编译入口文件:src/cmd/compile/main.go -> gc.Main(archInit)
// Phase 4: Decide how to capture closed variables.(决定如何捕获闭包变量)
// This needs to run before escape analysis,
// because variables captured by value do not escape.(变量捕获应该在逃逸分析之前进行,因为值类型的变量捕获,不会进行逃逸分析)
	timings.Start("fe", "capturevars")
	for _, n := range xtop {
		if n.Op == ODCLFUNC && n.Func.Closure != nil { //函数需要是闭包类型
			Curfn = n
			capturevars(n)
		}
	}
	capturevarscomplete = true

从上边这段代码及注释中,我们可以得到以下几个信息:

  • 变量捕获应该在逃逸分析之前进行,因为值类型的变量捕获,不会进行逃逸分析
  • 变量捕获是针对闭包函数的
  • 变量捕获的实现主要是调用了:src/cmd/compile/internal/gc/closure.go→capturevars

下边我们就去看capturevars方法的内部实现,了解变量捕获的一些细节

变量捕获底层实现

所有类型检查完成后,capturevars将在单独的阶段调用,它决定闭包捕获的每个变量是通过值还是通过引用捕获

func capturevars(xfunc *Node) {
	......
	clo := xfunc.Func.Closure
	cvars := xfunc.Func.Cvars.Slice()
	out := cvars[:0]
	for _, v := range cvars {
		......
		out = append(out, v)
		......
		outer := v.Name.Param.Outer
		outermost := v.Name.Defn
		// out parameters will be assigned to implicitly upon return.
		if outermost.Class() != PPARAMOUT && !outermost.Name.Addrtaken() && !outermost.Name.Assigned() && v.Type.Width <= 128 {
			v.Name.SetByval(true)
		} else {
			outermost.Name.SetAddrtaken(true)
			outer = nod(OADDR, outer, nil)
		}
		......
		outer = typecheck(outer, ctxExpr)
		clo.Func.Enter.Append(outer)
	}
	xfunc.Func.Cvars.Set(out)
	lineno = lno
}

该方法的代码量很少,大致内容就是,它会先获取到闭包函数内所有变量节点,然后对这些节点进行遍历。确定该闭包需要捕获的变量之后再没有被修改时,该变量小于128字节,则会认为他是值引用。后边它会对外部引用的结点进行类型检查

总结

本部分比较简单,但是挺实用的,特别是我这种一直搞不明包闭包引用外部变量的人。后边的逃逸分析、闭包重写跟变量捕获有一定的联系,介绍的后边内容的时候再提

以上就是Go语言编译原理之变量捕获的详细内容,更多关于Go编译原理变量捕获的资料请关注三水点靠木其它相关文章!

Golang 相关文章推荐
一文读懂go中semaphore(信号量)源码
Apr 03 Golang
Go 实现英尺和米的简单单位换算方式
Apr 29 Golang
golang slice元素去重操作
Apr 30 Golang
解决Golang中goroutine执行速度的问题
May 02 Golang
golang elasticsearch Client的使用详解
May 05 Golang
Golang 实现获取当前函数名称和文件行号等操作
May 08 Golang
Go timer如何调度
Jun 09 Golang
Go 语言下基于Redis分布式锁的实现方式
Jun 28 Golang
Golang表示枚举类型的详细讲解
Sep 04 Golang
Golang 并发下的问题定位及解决方案
Mar 16 Golang
Go语言特点及基本数据类型使用详解
Mar 21 Golang
GoFrame gredis缓存DoVar Conn连接对象 自动序列化GoFrame gredisDo/DoVar方法Conn连接对象自动序列化/反序列化总结
Jun 14 Golang
在ubuntu下安装go开发环境的全过程
Aug 05 #Golang
Go语言测试库testify使用学习
Jul 23 #Golang
Go语言怎么使用变长参数函数
Jul 15 #Golang
Go微服务项目配置文件的定义和读取示例详解
Jun 21 #Golang
Go本地测试解耦任务拆解及沟通详解Go本地测试的思路沟通的重要性总结
Jun 21 #Golang
Go 内联优化让程序员爱不释手
Jun 21 #Golang
GoFrame框架数据校验之校验结果Error接口对象
Jun 21 #Golang
You might like
php开发文档 会员收费1期
2012/08/14 PHP
php获取mysql字段名称和其它信息的例子
2014/04/14 PHP
使用PHP和JavaScript判断请求是否来自微信内浏览器
2015/08/18 PHP
php构造函数与析构函数
2016/04/23 PHP
php图像处理函数imagecopyresampled用法详解
2016/12/02 PHP
PHP7 整型处理机制修改
2021/03/09 PHP
JQuery与Ajax常用代码实现对比
2009/10/03 Javascript
jquery处理json对象
2014/11/03 Javascript
jQuery实现只允许输入数字和小数点的方法
2016/03/02 Javascript
使用getBoundingClientRect方法实现简洁的sticky组件的方法
2016/03/22 Javascript
Bootstrap每天必学之按钮(Button)插件
2016/04/25 Javascript
整理JavaScript对DOM中各种类型的元素的常用操作
2016/05/05 Javascript
require.js配合插件text.js实现最简单的单页应用程序
2016/07/12 Javascript
js css+html实现简单的日历
2016/07/14 Javascript
JS 事件绑定、事件监听、事件委托详细介绍
2016/09/28 Javascript
jquery+Jscex打造游戏力度条
2020/09/12 Javascript
微信小程序  modal详解及实例代码
2016/11/09 Javascript
Javascript实现找不同色块的游戏
2017/07/17 Javascript
angularjs实现时间轴效果的示例代码
2017/11/29 Javascript
微信小程序自定义导航栏实例代码
2019/04/05 Javascript
layer页面跳转,获取html子节点元素的值方法
2019/09/27 Javascript
小程序使用wxs解决wxml保留2位小数问题
2019/12/13 Javascript
js实现简单的点名器随机色实例代码
2020/09/20 Javascript
[28:48]《真视界》- 2017年国际邀请赛
2017/09/27 DOTA
[00:12]2018DOTA2亚洲邀请赛 Somnus丶M出阵单挑
2018/04/06 DOTA
Python基础之函数用法实例详解
2014/09/10 Python
在Django的视图(View)外使用Session的方法
2015/07/23 Python
微信跳一跳小游戏python脚本
2018/01/05 Python
Python多线程threading和multiprocessing模块实例解析
2018/01/29 Python
pygame游戏之旅 游戏中添加显示文字
2018/11/20 Python
python 实现两个npy档案合并
2020/07/01 Python
文史专业毕业生自荐信
2013/11/17 职场文书
工作态度不端正检讨书
2014/10/04 职场文书
MySQL主从搭建(多主一从)的实现思路与步骤
2021/05/13 MySQL
Python列表的索引与切片
2022/04/07 Python
win10壁纸在哪个文件夹 win10桌面背景图片文件位置分享
2022/08/05 数码科技