《程序员的自我修养》第二部分
第2章 编译和链接
流行的IDE把编译和链接合并到一起,这个过程叫构建(Build)
IDE会提供默认配置,隐藏大量细节,导致我们遇到在运行时种种性能瓶颈会束手无策
用gcc hello.c编译hello文件时会发生这些事:
预处理(Prepressing)、编译(Compilation)、汇编(Assembly)、链接(Linking)
预编译过程简单
源文件.c和相关头文件stdio.h等被预编译成cpp。
预编译过程:
1.预编译指 #include #define… 2.删除注释 3.添加行号、文件名标识 4.保留#pragme编译器指令
编译过程复杂,需要优化
词法分析、语法分析、语义分析及优化。之后生产相应的汇编代码.s文件
** 汇编**过程简单,直接翻译
输入.s 翻译成 .o机器可执行指令文件
汇编代码变成机器可执行的指令,每条汇编语句几乎都对应一条机器指令。
链接过程费解
输入需要链接的.o文件,链接后输出 .out文件可执行文件
编译过程
一般分6步:扫描、语法分析、语义分析、源代码优化、代码生成和目标代码优化
1.词法分析:
输入:扫描源代码,
再处理:用lex程序,按照用户之前描述好的词法规则将输入的字符串分割成一个个记号。
2.语法分析:只做语法分析,并不了解这个语句是否真正有意义。
输入:记号进行语法分析
输出:语法树
过程:词法分线器 生成语法树(以表达式为节点的树)。
array[index] = (index + 4) * (2 + 6)
符号、数字是最小表达式,不由其他表达式组成,通常作为语法树叶节点。
很多运算符号优先级,符号不同含义进行语法分析
工具:yacc可以根据给定的语法规则对输入的记号序列进行解析,构建语法树。所以只要改变语法规则,就能进行解析。跟lex词法分析器一样。只对表达式的语法层面分析,并不了解语句本身是否有意义。比如C语言两个指针做乘法运算是没有意义,在语法分析是合法。
3.语义分析:
两个相关概念:静态语义、动态语义
静态语义指编译期可以确定的语义。
动态语义指运行期才能确定的语义,比如0作为除数。
语义分析:只能分析静态语义中出现的错误,包括声明、类型的匹配、类型转换等。
4.中间语言生成 (编译器前端)
输入:语法树
输出:中间代码,不同编译器中间店面有很多种类。
编译器前端:负责产生机器无关代码
编译器后端将中间代码转换成目标机器代码。这个属于5的范畴
前端后端分开的好处:针对不同平台使用同一个前端,不同机器平台对应不同后端
5.目标代码生成与优化 (编译器后端)
编译器后端包括:生成器、目标代码优化器
生成器:输入:中间代码 输出:目标机器代码。这个过程依赖目标机器,字长不同。
如上图:右边是中间代码,经过生成器处理,变成左边目标机器码。
最后目标代码优化器对目标机器码进行优化。
链接器
产生的背景:
最开始是在一个文件内写所以代码,对文件修改后还要对各个目标的地址重新计算(重定向)。
从打卡纸带的程序到发明汇编语言。修改程序后,汇编器会重新计算“foo”目标地址。
汇编语言和汇编器解决了低级的繁琐的调整地址的工作,产生了符号这个概念。关于符号,它用来表示一个地址,这个地址可能是一段子程序(huo)的起始地址或者变量的起始地址。
按功能和性质划分代码,形成功能模块,模块按照层次结构或其他结构组织。
每个模块互相依赖又相对独立,可单独开发、编译、测试,改变部分代码不需要编译整个程序。
模块间通信有两种方式:一是模块间函数调用,二是模块间变量访问。两种方式可归结为一种方式,模块间符号的引用
模块拼接—静态链接
链接过程主要包括:地址和空间分配、符号决议和重定位。