4.5.3-1.4 构建初始化顺序

全局变量与全局常量之间可能存在相互依赖的情况,如下列代码所示:

// 常量依赖其它常量
const a = b + 5
const b = 1

// 变量依赖其它常量、变量以及函数
const golang = "Golang"

var java = "Java"
var greeting = "Hello: " + lang(golang)

func lang(l string) string {
	if len(l) {
		return java
	}
	return l
}

// 循环依赖
const a = b
const b = c
const c = a + 1

为了简化初始化时的执行逻辑,我们需要确定变量的初始化顺序,即按照其依赖关系的拓扑顺序进行初始化,同时也需要检测是否有循环依赖。该逻辑定义在文件$GCROOT/compile/internal/types2/initorder.go中,入口方法是check.initOrder()。该方法的效果就是初始化 info.InitOrder 字段。

在讨论Object对象时我们已经提到过,编译器特别定义了一个dependency对象来标识那些可用于初始化表达式的对象,所有的 Const, Var 以及 Func 对象都属于 dependency 对象。

对象的依赖关系保存在declInfodeps字段中,在对该对象进行类型检查时,检查器通过函数func (d *declInfo) addDep(obj Object) {}将当前对象所依赖的对象保存起来。

计算初始化顺序的算法思路如下:先构建对象依赖关系的有向图(Directed Graph),再以每个节点的依赖数目为权重构建最小堆(Min Heap)并以此堆作为最小优先级队列(Priority Queue),因此队列头部的对象总是依赖其它对象最少的,所以该队列的遍历顺序就是初始化的顺序。

info.InitOrder 内只包含全局变量的初始化逻辑,常量的初始化在类型检查时已经完成,这里处理常量的依赖图只是为了检测循环依赖。

最后更新于