编译原理视角看Go程序是怎样跑起来的

2023年 7月 14日 56.6k 0

引入

我们从一个 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").}

编译原理视角看Go程序是怎样跑起来的

和 ascii 一对比,就能发现,中间的列和最右边的一一对应。也就是说,刚写完的 hello.go 是由ascii字符表示的。被称为文本文件。

go程序并不能直接运行,每条 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程序是怎样跑起来的

词法分析

通过前面的例子,我们知道,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: "

相关文章

JavaScript2024新功能:Object.groupBy、正则表达式v标志
PHP trim 函数对多字节字符的使用和限制
新函数 json_validate() 、randomizer 类扩展…20 个PHP 8.3 新特性全面解析
使用HTMX为WordPress增效:如何在不使用复杂框架的情况下增强平台功能
为React 19做准备:WordPress 6.6用户指南
如何删除WordPress中的所有评论

发布评论