此系列为我学习经典的读书笔记,目的是理清知识脉络帮助理解记忆,内容深度、质量远远不及原书。如果您对相关知识感兴趣,强烈建议阅读原书程序员的自我修养
带着问题学习
- 源代码到可执行文件都经历了哪些步骤
- 编译器做了什么工作
- 什么是静态链接,静态链接都包含哪些步骤
可执行文件的生成
源代码到可执行文件的生成可分为预处理(Prepressing)、编译(Compilation)、汇编(Assembly)和链接(Linking),四个步骤。
预处理
以 C 语言为例,预处理主要是处理源代码中以“#”开头的那些预处理指令,规则如下:
- 将所有 “#define” 删除并展开宏定义
- 处理所有条件预编译指令比如 “#if”、“#ifdef”、“#elif”、“#else”、“#endif”
- 处理 “#include” 预编译指令,将被包含的文件插入到预编译位置。递归进行,被包含文件可能还包含其他文件
- 删除所有注释
- 添加行号与文件名标识,以便编译器产生调试信息
- 保留 #pragma 编译器指令,因为编译器必须使用它们
编译
编译过程就是把预处理完的文件进行一系列词法分析、语法分析、语义分析以及优化后产生相应的汇编文件。
汇编
汇编是汇编器将汇编代码转变成机器可执行的指令的过程。汇编指令与机器指令几乎一一对应,所以汇编器直接翻译就可以了。
链接
将中间文件进行链接最终生成可执行文件。(此篇文章主要记录编译器工作,链接相关以后会分析到)
编译器做了什么
简单来说,编译器就是将高级语言翻译成机器语言的一个工具。
我们以一行 C 语言为例:
array[index] = (index + 4) * (2 + 6) CompilerExpression.c
词法分析
首先源代码进入扫描器(Scanner),扫描器利用有限状态机(Finite State Machine)算法将源代码的字符序列分割成一系列符号(Token)。
记号 | 类型 |
---|---|
array | 标识符 |
[ | 左方括号 |
index | 标识符 |
] | 右方括号 |
= | 赋值 |
( | 左圆括号 |
index | 标识符 |
+ | 加号 |
4 | 数字 |
) | 右圆括号 |
* | 乘号 |
( | 左圆括号 |
2 | 数字 |
+ | 加号 |
6 | 数字 |
) | 右圆括号 |
词法分析产生的符号分为以下几类:关键字、标识符、字面量(包含数字、字符串等)和特殊符号(如加号、等号)。
与此同时,扫描器也完成了将标识符放入符号表,将数字、字符串常量放入文字表等工作,以备后续步骤使用。
语法分析
语法分析器(Grammar Parser)对扫描器生成的符号进行语法分析,整个过程采取上下文无关语法(Context-free Grammar)分析法,生成语法树,就是以表达式(Expression)为结点的树。
语义分析
接下来就是语义分析器(Semantic Analyzer)对表达式进行语义层面的分析。
编译器所能分析的语义叫做静态语义(Static Semantic),也就是在编译期可以确定的语义,与之对应的叫动态语义(Dynamic Semantic),要到运行期才能确定的语义。
静态语义包括声明和类型的匹配,类型的转换。比如浮点型表达式赋值给整型表达式,这里就隐含了类型转换工作。
动态语义一般是运行期出现的相关问题,比如将 0 作为除数就是一个运行期语义错误。
中间语言生成
源代码优化器(Source Code Optimizer)会将整个语法树转换成中间代码(Intermediate Code)。比如 (2 + 6)就会转换成 8。
中间代码使得编译器可以被分为前端和后端,前端负责产生和机器无关的中间代码,后端将中间代码转换为目标机器代码。这样一些跨平台编译器可以共用一套前端,针对不同平台生成不同后端即可。
目标代码生成与优化
代码生成器(Code Generator)将中间代码转化为目标机器代码。 然后目标代码优化器(Target Code Generator)对目标代码进行优化,比如选择合适的寻址方式。
经过这么多步骤编译器最终将源代码编译成未链接的目标文件。如图:
静态链接
链接的主要过程包括地址与空间分配(Address and Storage Allocation)、符号决议(Symbol Resolution)和重定位(Relocation)等步骤。
最基本的静态链接过程,每个模块的源代码文件经过编译器的编译,形成目标文件(Object File,.o 或 .obj),目标文件和库(Library)一起链接形成最终的可执行文件。
比如目标文件 A 中一个变量 Foo 再链接了目标文件 B 之后才能确定地址。确定后,链接器就要对这个地址进行修正,这个修正的过程就叫重定位(Relocation),每个要被修正的地方叫一个重定位入口(Relocation Entry)。
总结
本篇简述了源代码到可执行文件的 4 个步骤:预编译、编译、汇编和链接。
其中详细介绍了编译过程中的:词法分析、语法分析、语义分析、中间代码生成和目标代码的生成与优化。
最后介绍了一些链接过程中的基本概念:重定位、目标文件、库等。
经过刚才的梳理,可以回答开头的问题了吗,如果还不能,或者想了解更多,欢迎阅读原书。如果发现了我的错误与纰漏,也请不吝赐教,十分感谢。