Go語言編譯原理之變量捕獲
前言
在前邊的幾篇文章中已經(jīng)基本分享完了編譯器前端的一些工作,后邊的幾篇主要是關(guān)于編譯器對抽象語法樹進(jìn)行分析和重構(gòu),然后完成一系列的優(yōu)化,其中包括以下五個部分:
- 變量捕獲
- 函數(shù)內(nèi)聯(lián)
- 逃逸分析
- 閉包重寫
- 遍歷函數(shù)
后邊的五篇文章主要就是上邊這五個主題,本文分享的是變量捕獲,變量捕獲主要是針對閉包場景的,因為閉包函數(shù)中可能引用閉包外的變量,因此變量捕獲需要明確在閉包中通過值引用或地址引用的方式來捕獲變量
變量捕獲概述
下邊通過一個示例來看一下什么是變量捕獲
package main
import (
"fmt"
)
func main() {
a := 1
b := 2
go func() {
//在閉包里對a或b進(jìn)行了重新賦值,也會改變引用方式
fmt.Println(a, b)
}()
a = 666
}
我們可以看到在閉包中引用了外部的變量a、b,由于變量a在閉包之后進(jìn)行了其他賦值操作,因此在閉包中,a、b變量的引用方式會有所不同。在閉包中,必須采取地址引用的方式對變量a進(jìn)行操作,而對變量b的引用將通過直接值傳遞的方式進(jìn)行
我們可以通過如下方式查看當(dāng)前程序閉包變量捕獲的情況
go tool compile -m=2 main.go | grep capturing

assign=true代表變量a在閉包完成后又進(jìn)行了賦值操作
也可以看一個稍微復(fù)雜的
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))
}
}
上一篇文章分享了類型檢查,我們可以繼續(xù)順著編譯的入口文件中類型檢查后邊的代碼往下看,你會看到如下這段代碼
編譯入口文件: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.(變量捕獲應(yīng)該在逃逸分析之前進(jìn)行,因為值類型的變量捕獲,不會進(jìn)行逃逸分析)
timings.Start("fe", "capturevars")
for _, n := range xtop {
if n.Op == ODCLFUNC && n.Func.Closure != nil { //函數(shù)需要是閉包類型
Curfn = n
capturevars(n)
}
}
capturevarscomplete = true
從上邊這段代碼及注釋中,我們可以得到以下幾個信息:
- 變量捕獲應(yīng)該在逃逸分析之前進(jìn)行,因為值類型的變量捕獲,不會進(jìn)行逃逸分析
- 變量捕獲是針對閉包函數(shù)的
- 變量捕獲的實現(xiàn)主要是調(diào)用了:src/cmd/compile/internal/gc/closure.go→
capturevars
下邊我們就去看capturevars方法的內(nèi)部實現(xiàn),了解變量捕獲的一些細(xì)節(jié)
變量捕獲底層實現(xiàn)
所有類型檢查完成后,capturevars將在單獨的階段調(diào)用,它決定閉包捕獲的每個變量是通過值還是通過引用捕獲
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
}
該方法的代碼量很少,大致內(nèi)容就是,它會先獲取到閉包函數(shù)內(nèi)所有變量節(jié)點,然后對這些節(jié)點進(jìn)行遍歷。確定該閉包需要捕獲的變量之后再沒有被修改時,且該變量小于128字節(jié),則會認(rèn)為他是值引用。后邊它會對外部引用的結(jié)點進(jìn)行類型檢查
總結(jié)
本部分比較簡單,但是挺實用的,特別是我這種一直搞不明包閉包引用外部變量的人。后邊的逃逸分析、閉包重寫跟變量捕獲有一定的聯(lián)系,介紹的后邊內(nèi)容的時候再提
以上就是Go語言編譯原理之變量捕獲的詳細(xì)內(nèi)容,更多關(guān)于Go編譯原理變量捕獲的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
golang實現(xiàn)實時監(jiān)聽文件并自動切換目錄
這篇文章主要給大家介紹了golang實現(xiàn)實時監(jiān)聽文件,并自動切換目錄,文中通過代碼示例給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作有一定的參考價值,需要的朋友可以參考下2023-12-12
Go?1.21新內(nèi)置函數(shù)min、max和clear的用法詳解
Go?1.21?版本已經(jīng)正式發(fā)布,它帶來了許多新特性和改進(jìn),其中引入了的三個新內(nèi)置函數(shù):max、min?和?clear,接下來我們就來看看這些函數(shù)的用途和特點吧2023-08-08

