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

目录
  • 前言
  • 变量捕获概述
  • 变量捕获底层实现
  • 总结

前言

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

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

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

变量捕获概述

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

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

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编译原理变量捕获的资料请关注我们其它相关文章!

(0)

相关推荐

  • 优雅使用GoFrame共享变量Context示例详解

    目录 前言摘要 Context是什么? 为什么需要Context? Context是如何实现共享变量的? 如何使用? 一.结构定义 介绍 二.逻辑封装 三.上下文变量注入 四.上下文变量使用 方法定义 Context对象获取 自定义Key-Value 五.注意问题 总结 前言摘要 昨天和同事merge代码又遇到了很多冲突,发现之前有些方法写的参数不规范,没有传入Context,不方便进行链路追踪.他在review项目代码,基本把项目中的方法都加了Context参数. 今天就为大家介绍一下Cont

  • golang如何用type-switch判断interface变量的实际存储类型

    目录 用type-switch判断interface的实际存储类型 type-switch就刚好帮我解决了这个问题 golang任何类型interface{} example1 example2 用type-switch判断interface的实际存储类型 在go语言中,interface很好用,但是往往我们无法确定interface里存储的到底是什么类型,而且go又是强类型语言. type-switch就刚好帮我解决了这个问题 //例子 var s interface{} switch s.(

  • Go基础教程系列之import导入包(远程包)和变量初始化详解

    import导入包 搜索路径 import用于导入包: import ( "fmt" "net/http" "mypkg" ) 编译器会根据上面指定的相对路径去搜索包然后导入,这个相对路径是从GOROOT或GOPATH(workspace)下的src下开始搜索的. 假如go的安装目录为/usr/local/go,也就是说GOROOT=/usr/local/go,而GOPATH环境变量GOPATH=~/mycode:~/mylib,那么要搜索net

  • GoFrame通用类型变量gvar与interface基本使用对比

    目录 前言摘要 通用变量 gvar 使用场景 看源码学编程 如何设置并发安全开关呢? 基本使用 打印结果 序列化示例 打印结果 总结 前言摘要 这篇文章将介绍 GoFrame 通用类型变量gvar的概念,对比 interface{}的特点:以及如何设置gvar的并发安全开关等基础使用:介绍序列化示例代码. 通用变量 gvar gvar 通用动态变量,支持各种内置的数据类型转换,可以作为interface{}类型的替代数据类型,并且该类型支持并发安全开关. 使用场景 所有需要使用interface

  • Go获取与设置环境变量的方法详解

    目录 前言 01 从安装 Go 说起 02 Go 如何使用环境变量 03 小结 前言 今天的文章比较基础,但却是必须掌握的,而且本文有些内容,也许你之前没想过.希望这篇文章能够让你理解环境变量并掌握 Go 环境变量相关操作. 01 从安装 Go 说起 其实不止是安装 Go,其他语言一本也会有类似的问题.一般来说,安装完 Go 后,会建议将 go 可执行程序配置到 PATH 环境变量中. 比如我本地的 PATH 环境变量的值: $ echo $PATH /Users/xuxinhua/.go/bi

  • Go语言基础变量的声明及初始化示例详解

    目录 一.概述 二.声明变量 三.编译器推导类型的格式[一定要赋值] 四.短变量声明并初始化 五.匿名变量--没有名字的变量 六.注意 七.案例 一.概述 变量的功能是存储用户的数据 二.声明变量 Go语言的每一个变量都拥有自己的类型,必须经过声明才能开始用 变量的声明格式: var <变量名称> [变量类型] var a int //声明一个整型类型的变量,可以保存整数数值 var b string //声明一个字符串类型的变量 var c float32 //声明一个32位浮点切片类型的变

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

    目录 前言 变量捕获概述 变量捕获底层实现 总结 前言 在前边的几篇文章中已经基本分享完了编译器前端的一些工作,后边的几篇主要是关于编译器对抽象语法树进行分析和重构,然后完成一系列的优化,其中包括以下五个部分: 变量捕获 函数内联 逃逸分析 闭包重写 遍历函数 后边的五篇文章主要就是上边这五个主题,本文分享的是变量捕获,变量捕获主要是针对闭包场景的,因为闭包函数中可能引用闭包外的变量,因此变量捕获需要明确在闭包中通过值引用或地址引用的方式来捕获变量 变量捕获概述 下边通过一个示例来看一下什么是变

  • Go语言编译原理之源码调试

    目录 前言 Goland的debug调试Go源码 dlv工具调试Go源码 安装 常用命令 dlv调试抽象语法树构建 前言 在前边几篇文章中分享了Go编译过程中的源码实现,本文主要是想分享一下我是怎么调试Go的源代码的(如果你很熟悉的话,可以跳过本文).本文主要是分享两种Go源码的调试方法 Goland的debug dlv工具 本文我还会以抽象语法树为例,来通过dlv对它的构建过程进行调试 Goland的debug调试Go源码 下边以调试Go编译的入口文件为例 编辑debug配置 填写配置信息 打

  • Go编译原理之函数内联

    目录 前言 函数内联概述 函数内联底层实现 visitBottomUp caninl inlcalls 前言 在前一篇文章中分享了编译器优化的变量捕获部分,本文分享编译器优化的另一个内容—函数内联.函数内联是指将将较小的函数内容,直接放入到调用者函数中,从而减少函数调用的开销 函数内联概述 我们知道每一个高级编程语言的函数调用,成本都是在与需要为它分配栈内存来存储参数.返回值.局部变量等等,Go的函数调用的成本在于参数与返回值栈复制.较小的栈寄存器开销以及函数序言部分的检查栈扩容(Go语言中的栈

  • JavaScript 详解预编译原理

    JavaScript 预编译原理 今天用了大量时间复习了作用域.预编译等等知识 看了很多博文,翻开了以前看过的书(好像好多书都不会讲预编译) 发现当初觉得自己学的很明白,其实还是存在一些思维误区 (很多博文具有误导性) 今晚就整理了一下凌乱的思路 先整理一下预编译的知识吧,日后有时间再把作用域详细讲解一下 大家要明白,这个预编译和传统的编译是不一样的(可以理解js预编译为特殊的编译过程) JavaScript是解释型语言, 既然是解释型语言,就是编译一行,执行一行 传统的编译会经历很多步骤,分词

  • 详解编译器编译原理

    详解编译器编译原理 什么是gcc  什么是gcc:gcc是GNU Compiler Collection的缩写.最初是作为C语言的编译器(GNU C Compiler),现在已经支持多种语言了,如C.C++.Java.Pascal.Ada.COBOL语言等. gcc支持多种硬件平台,甚至对Don Knuth 设计的 MMIX 这类不常见的计算机都提供了完善的支持 gcc主要特征  1)gcc是一个可移植的编译器,支持多种硬件平台 2)gcc不仅仅是个本地编译器,它还能跨平台交叉编译. 3)gcc

  • 深入了解Vue3模板编译原理

    目录 Parse Transform cacheHandlers hoistStatic prefixIdentifiers PatchFlags hoists type 变化 Codegen 代码生成模式 静态节点 帮助函数 helpers helpers 是怎么使用的呢? 如何生成代码? Vue 的编译模块包含 4 个目录: compiler-core compiler-dom // 浏览器 compiler-sfc // 单文件组件 compiler-ssr // 服务端渲染 其中 com

  • Go语言底层原理互斥锁的实现原理

    目录 Go 互斥锁的实现原理? 概念 使用场景 底层实现结构 操作 加锁 解锁 Go 互斥锁正常模式和饥饿模式的区别? 正常模式(非公平锁) 饥饿模式(公平锁) Go 互斥锁允许自旋的条件? Go 互斥锁的实现原理? Go sync包提供了两种锁类型:互斥锁sync.Mutex 和 读写互斥锁sync.RWMutex,都属于悲观锁. 概念 Mutex是互斥锁,当一个 goroutine 获得了锁后,其他 goroutine 不能获取锁(只能存在一个写者或读者,不能同时读和写) 使用场景 多个线程

  • swift语言AutoreleasePool原理及使用场景

    目录 使用场景 NSAutoreleasePool @autoreleasepool __autoreleasing 源码分析 __AtAutoreleasePool结构体 AutoreleasePoolPage POOL_BOUNDARY 多层嵌套 push autoreleaseFast autoreleaseFullPage autoreleaseNoPage add pop popPage releaseUntil autorelease hotPage coldPage 调试 _obj

  • sublime text3搭建配置c语言编译环境的详细图解教程(小白级)

    目录 使用sublime text3编译c语言部分 准备工作 配置环境变量 新建sublime text3的c语言编译环境 举例 软件下载可以在官网下载,均为免费软件 有问题可以联系我邮箱求助:sexluna@outlook.com sublime text3虽然收费但是支持无限期试用且功能不受限制(免费软件) 使用sublime text3编译c语言部分 准备工作 下载sublime text3 配置计算机c语言编译环境,下载MinGW即可 配置MinGW的系统变量 将下载解压的MinGW的b

  • 浅谈Sizzle的“编译原理”

    Sizzle,是jQuery作者John Resig写的DOM选择器引擎,速度号称业界第一.作为一个独立全新的选择器引擎,出现在jQuery 1.3版本之后,并被John Resig作为一个开源的项目.Sizzle是独立的一部分,不依赖任何库,如果你不想用jQuery,可以只用Sizzle,也可以用于其他框架如:Mool, Dojo,YUI等. 前几天在准备一个关于jQuery的分享PPT,问同事关于jQuery除了使用方法之外还有没有其他特别想了解一下的,有人提到了想了解下它的选择器是怎么实现

随机推荐