前文
package main
import "fmt"
func main() {
fmt.Println("Hello, world")
}
- 在本文中,我们将介绍初学者比较关心的话题:go语言如何编译为机器码
- 本文的目标是希望读者对go语言的编译过程有一个全面的理解
- 一段程序要运行起来,需要将go代码生成机器能够识别的二进制代码
- go代码生成机器码需要编译器经历:
词法分析 => 语法分析 => 类型检查 => 中间代码 => 代码优化 => 生成机器码
- Go语言的编译器入口是
src/cmd/compile/internal/gc
包中的 main.go
文件,此函数会先获取命令行传入的参数并更新编译的选项和配置
- 随后就会开始运行 parseFiles 函数对输入的所有文件进行词法与语法分析
func Main(archInit func(*Arch)) {
// ...
lines := parseFiles(flag.Args())
词法分析
- 所有的编译过程都是从解析代码的源文件开始的
- 词法分析的作用就是解析源代码文件,它将文件中的字符串序列转换成
Token
序列,方便后面的处理和解析
- 我们一般会把执行词法分析的程序称为词法解析器(lexer)
Token
可以是关键字,字符串,变量名,函数名
- 有效程序的"单词"都由
Token
表示,具体来说,这意味着"package","main","func" 等单词都为Token
- Go语言允许我们使用go/scanner和go/token包在Go程序中执行解析程序,从而可以看到类似被编译器解析后的结构
- 如果在语法解析的过程中发生了任何语法错误,都会被语法解析器发现并将消息打印到标准输出上,整个编译过程也会随着错误的出现而被中止
- helloworld程序解析后如下所示
1:1 package "package"
1:9 IDENT "main"
1:13 ; "\n"
2:1 import "import"
2:8 STRING "\"fmt\""
2:13 ; "\n"
3:1 func "func"
3:6 IDENT "main"
3:10 ( ""
3:11 ) ""
3:13 { ""
4:3 IDENT "fmt"
4:6 . ""
4:7 IDENT "Println"
4:14 ( ""
4:15 STRING "\"Hello, world!\""
4:30 ) ""
4:31 ; "\n"
5:1 } ""
5:2 ; "\n"
5:3 EOF ""
- 我们可以看到,词法解析器添加了分号,分号常常是在C语言等语言中一条语句后添加的
- 这解释了为什么Go不需要分号:词法解析器可以智能地加入分号
语法分析
- 语法分析的输入就是词法分析器输出的 Token 序列,这些序列会按照顺序被语法分析器进行解析,语法的解析过程就是将词法分析生成的 Token 按照语言定义好的文法(Grammar)自下而上或者自上而下的进行规约,每一个 Go 的源代码文件最终会被归纳成一个 SourceFile 结构:
SourceFile = PackageClause ";" { ImportDecl ";" } { TopLevelDecl ";" }
- 标准的 Golang 语法解析器使用的就是 LALR(1) 的文法,语法解析的结果生成了抽象语法树(Abstract Syntax Tree,AST)
- 抽象语法树(Abstract Syntax Tree,AST),或简称语法树(Syntax tree),是源代码语法结构的一种抽象表示。它以树状的形式表现编程语言的语法结构,树上的每个节点都表示源代码中的一种结构。</