4.5.1 类型检查逻辑 - 包加载器
go 编译器一次编译的最大单元是一个包,输出的结果叫着对象文件(Object File),go 定义了自己的对象文件格式,并以
.a
或者.o
为后缀存在于文件系统中。在$GOROOT/pkg/$OS_$ARCH/
目录下就包含了所有标准库的对象文件,例如 linux 系统该目录为:$GOROOT/pkg/linux_amd64
.加载器在编译阶段解决包依赖问题,其任务就是通过包名找到对应的对象文件,然后将其解析 成为上文提到的Package对象。加载器可以有各种实现,其接口定义在文件
$GCROOT/compile/internal/types2/api.go
中:type Importer interface {
Import(path string) (*Package, error)
}
type ImportMode int
type ImporterFrom interface {
Importer
ImportFrom(path, dir string, mode ImportMode) (*Package, error)
}
两个接口的区别是方法
ImporterFrom
通过指定目标目录的方式来支持vendor 机制, 而Importer
无法支持,保留Importer
接口是为了兼容性考虑,ImportMode 当前没有用途。加载器的实现在$GCROOT/compile/internal/noder/import.go
中,抛开具体细节,其代码框架为:type gcimports struct {
packages map[string]*types2.Package
}
func (m *gcimports) Import(path string) (*types2.Package, error) {
return m.ImportFrom(path, "" /* no vendoring */, 0)
}
func (m *gcimports) ImportFrom(path, srcDir string, mode types2.ImportMode) (*types2.Package, error) { /* 忽略方法体 */
}
可见
Import
只是简单地调用ImportFrom
方法,而后者负责整个加载解析逻辑。加载器配置在Config中,并在类型检查的入口函数check2()
中进行初始化,该函数在文件$GCROOT/compile/internal/noder/irgen.go
中。go 编译过程的最后一件事情就是将编译结果导出成对象文件,所以包加载器只是该过程的一个逆操作,当前加载器的实现也是 Go 编译器团队在重写类型检查器的过程中临时的一个实现,后续可能会修改。
对于对象文件的详细格式、导出逻辑与加载逻辑我们没有必要深挖细刨,只需要掌握如下要点即可:
- 1.对象文件是二进制格式文件,是编译器的输出,链接器的输入
- 2.对象文件分为不同的块(Section)来组织数据,例如数据块, 代码块,调试信息块等
- 3.对象文件内包含重定位(Relocation)信息,链接器在将多个对象链接在一起时,主要工作就是为需要重定位的数据或者指令规划内存地址
- 4.go 是静态链接语言,在生成可执行文件时,链接器会将所有依赖的包链接在一起,形成一个独立的可执行文件
- 5.可以通过工具
go tool objdump
来查看对象文件或者可执行文件的内容