文章目录
程序员的自我修养
1.基础知识
2.编译和链接
2.1被隐藏的过程
IDE一般都将编译和链接的过程一步完成,通常将这 种编译和链接合并到一起的过程称为构建(Build)。即使使用命令行 来编译一个源代码文件,简单的一句“gcc hello.c”命令就包含了非常复 杂的过程。
#include <stdio.h>
int main()
{
printf("Hello World\n");
return 0;
}
hello.c
编译执行:
$gcc hello.c
$./a.out
Hello World
上述过程可以分解为4个步骤:
- 预处理(Prepressing)
- 编译(Compilation)
- 汇编(Assembly)
- 链接(Linking)
2.1.1预编译
$gcc –E hello.c –o hello.i
或 $cpp hello.c > hello.i
将源代码文件hello.c和相关的头文件(stdio.h)等被预编译器cpp预编译成一个.i文件
2.1.2 编译
$gcc –S hello.i –o hello.s
把预处理完的文件进行一系列词法分析、语法分析、语义分析及优化后生产相应的汇编代码文件
2.1.3 汇编
$as hello.s –o hello.o
将汇编代码转变成机器可以执行的指令,每一个汇编语句几乎 都对应一条机器指令,目标文件
2.1.4 链接
$ld -static /usr/lib/crt1.o /usr/lib/crti.o /usr/lib/gcc/i486-linuxgnu/4.1.3/crtbeginT.o -L/usr/lib/gcc/i486-linux-gnu/4.1.3 -L/usr/lib - L/lib hello.o --start-group -lgcc -lgcc_eh -lc --end-group /usr/lib/gcc/i486- linux-gnu/4.1.3/crtend.o /usr/lib/crtn.o
要将一大堆文件链接起来才可以得到“a.out”,即最终的可执行文件
2.2 编译器做了什么
编译过程一般可以分为6步:
- 扫描
- 语法分析
- 语义分析
- 源代码优化
- 代码生成
- 目标代码优化
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-CIRUgEVF-1671542167853)(程序员的自我修养_img/image-20221201203506799.png)]
示例代码:
array[index] = (index + 4) * (2 + 6)
CompilerExpression.c
2.2.1 词法分析
源代码程序被输入到扫描器(Scanner),扫描器简单地进行词法分析
词法分析产生的记号分为:关键字、标识符、字面量 (包含数字、字符串等)和特殊符号(如加号、等号)
同时也将标识符存放到符号表,将数字、字符串常量存放到文字表等
2.2.2 语法分析
语法分析器(Grammar Parser)将对由扫描器产生的记号进行语 法分析,从而产生语法树(Syntax Tree)
2.2.3 语义分析
编译器所能分析的语义是静态语义 (Static Semantic),所谓静态语义是指在编译期可以确定的语义,与之对应的动态语义(Dynamic Semantic)就是只有在运行期才能确定的语义。
经过语义分析阶段以后,整个语法树的表达式都被标识了类型,如果有 些类型需要做隐式转换,语义分析程序会在语法树中插入相应的转换节点。
语义分析器还对符号表里的符号类型也做了更新
2.2.4 中间语言生成
直接在语法树上作优化比较困难,所以源代码优化器往往将整个语法树转换成中间代码 (Intermediate Code),它是语法树的顺序表示,其实它已经非常接近目标代码了
上面的例子中的语法树可以被翻译成三地址码:
t1 = 2 + 6
t2 = index + 4
t3 = t2 * t1
array[index] = t3
为了使所有的操作都符合三地址码形式,这里利用了几个临时变量:t1、t2和t3,优化后的三地址码:
t2 = index + 4
t2 = t2 * 8
array[index] = t2
中间代码使得编译器可以被分为前端和后端。编译器前端负责产生机器无关的中间代码,编译器后端将中间代码转换成目标机器代码。这样对 于一些可以跨平台的编译器而言,它们可以针对不同的平台使用同一个前端和针对不同机器平台的数个后端。
2.2.5 目标代码生成与优化
编译器后端主要包括代码生成器(Code Generator)和目标代码优化器 (Target Code Optimizer)
- 代码生成器将中间代码转换成目标机器代码,这个过程十分依赖于目标机器,因为 不同的机器有着不同的字长、寄存器、整数数据类型和浮点数数据类型等。
代码生成器可能会生成下面的代码序列(用x86的汇编语言表示):
movl index, %ecx ; value of index to ecx
addl $4, %ecx ; ecx = ecx + 4
mull $8, %ecx ; ecx = ecx