引入
我们从一个 helloworld 的例子开始
package main
import "fmt"
func main() {
fmt.Println("hello world")
}
用 vim 要打开,输入命令:
:%!xxd
下面是输出
00000000:7061 636b 6167 6520 6d61 696e 0a0a 696d package main.. im
00000010:706f 7274 2022 666d 7422 0a0a 6675 6e63 port "fmt".. func
00000020:206d 6169 6e28 2920 7b0a 2020 2020 666d main(){. fm
00000030:742e 5072 696e 746c 6e28 2268 656c 6c6f t. Println("hello
00000040:2077 6f72 6c64 2229 0a7d 0a world").}
和 ascii 一对比,就能发现,中间的列和最右边的一一对应。也就是说,刚写完的 hello.go 是由ascii字符表示的。被称为文本文件。
go程序并不能直接运行,每条 go 语句必须转换为一系列的低级机器语言指令,将这些指令
打包到一起,并以二进制格式存储,也就是可执行文件。
从源文件到可执行目标文件的转化过程:
完成以上各个阶段的就是 go 编译系统。你肯定听过 GCC,它支持 C/C++/JAVA/PYTHON/OBJECTIV-CAADA/FORTRAN/PASCAL,能够为很多不同的机器生成机器码。
可执行目标文件可以直接在机器上执行。一般而言,
- 先执行一些初始化的工作;
- 找到main函数的入口,执行用户写的代码;
- 执行完成后,main 函数退出;
- 再执行一些收尾的工作,整个过程完毕。
接下来,我们将探索 编译 和 运行 的过程
编译链接
Go 源码里的编译器源码位于 src/cmd/compile
路径下,链接器源码位于 src/cmd/link
路径下。
编译过程
我比较喜欢用 IDE(集成开发环境)来写代码, Go 源码用的 Goland,有时候直接点击 IDE 菜单栏里的“运行”按钮,程序就跑起来了。这实际上隐含了编译和链接的过程,我们通常将编译和链接合并到一起的过程称为构建(Build)。
编译过程就是对源文件进行词法分析、语法分析、语义分析、优化,最后生成汇编代码文件,以 .s
作为文件后缀。
之后,汇编器会将汇编代码转变成机器可以执行的指令。由于每一条汇编语句几乎都与一条机器指令相对应,所以只是一个简单的一一对应,比较简单,没有语法、语义分析,也没有优化这些步骤。 编译器是将高级语言翻译成机器语言的一个工具,编译过程一般分为 6 步:扫描、语法分析、语义分析、源代码优化、代码生成、目标代码优化。下图来自《程序员的自我修养》:
词法分析
通过前面的例子,我们知道,Go 程序文件在机器看来不过是一堆二进制位。我们能读懂,是因为 Goland 按照 ASCII 码(实际上是 UTF-8)把这堆二进制位进行了编码。例如,把 8个 bit 位分成一组,对应一个字符,通过对照 ASCII 码表就可以查出来。 当把所有的二进制位都对应成了 ASCII 码字符后,我们就能看到有意义的字符串。它可能是关键字,例如:package;可能是字符串,例如:“Hello World”。
词法分析其实干的就是这个。输入是原始的 Go 程序文件,在词法分析器看来,就是一堆二进制位,根本不知道是什么东西,经过它的分析后,变成有意义的记号。简单来说,词法分析是计算机科学中将字符序列转换为标记(token)序列的过程。
我们来看一下维基百科上给出的定义:
词法分析(lexical analysis)是计算机科学中将字符序列转换为标记(token)序列的过程。进行词法分析的程序或者函数叫作词法分析器(lexical analyzer,简称lexer),也叫扫描器(scanner)。词法分析器一般以函数的形式存在,供语法分析器调用。
.go
文件被输入到扫描器(Scanner),它使用一种类似于 有限状态机
的算法,将源代码的字符系列分割成一系列的记号(Token)。 记号一般分为这几类:关键字、标识符、字面量(包含数字、字符串)、特殊符号(如加号、等号)。 例如,对于如下的代码:
slice[i] = i * (2 + 6)
总共包含 16 个非空字符,经过扫描后
记号 | 类型 |
---|---|
slice | 标识符 |
[ | 左方括号 |
i | 标识符 |
] | 右方括号 |
= | 赋值 |
i | 标识符 |
* | 乘号 |
( | 左圆括号 |
2 | 数字 |
+ | 加号 |
6 | 数字 |
) | 右圆括号 |
Go 语言(本文的 Go 版本是 1.9.2)扫描器支持的 Token 在源码中的路径:
src/cmd/compile/internal/syntax/token.go
感受一下:
var tokstrings = [...]string{
// source control
_EOF: "EOF",
// names and literals
_Name: "name",
_Literal:"literal",
// operators and operations
_Operator:"op",
_AssignOp:"op=",
_IncOp: "opop",
_Assign: "=",
_Define: ":=",
_Arrow: "