编译和链接
编译和链接: 通常将编译和链接合并到一起的过程称为 构建(Build),可以分解成以下 4 个步骤
预编译(Propressing)
源文件 和 头文件 被预编译器预编译成一个 .i 或者 .ii(cpp)的文件
期间主要处理那些源代码文件中的以 # 开始的预编译命令,比如 #include,#define 等
经过预编译后的 .i 文件 不包含任何宏定义,并且引用的文件已经全部被插入到 .i 文件中,删除了所有注释,处理了所有条件预编译指令(#if,#ifdef,#elif,#else,#endif),保留了所有#progma编译器命令
为此,当需要判断 宏定义,包含的头文件是否正确时,可以查看预编译后的文件来确定问题(gcc -E hello.c -o hello.i)
编译(Compilation)
编译过程就是把预编译完的文件(.i / .ii)进行一些列 词法分析,语法分析,语义分析及优化 后生成相应的 汇编代码文件(.s)
如今GCC已经把预编译和编译两个步骤合二为一了,使用叫做 cc1 的程序来完成(gcc -S hello.c -o hello.s)
实际 gcc 这个命令只是这些后台程序的 包装,它会根据不同的参数要求去调用预编译编译程序 cc1,汇编器 as,链接器 Id
汇编(Assembly)
汇编过程只是根据汇编指令和机器指令的对照表一一翻译就可以了(gcc -c hello.s -o hello.o),生成 目标文件(Object File)
链接(Linking)
目标文件 到 可执行文件,需要链接许多内容(静态链接和动态链接),后文会详细介绍
ld -static crt1.o crti.o crtbeginT.o hello.o –start-group -lgcc -lgcc_eh -lc -end-group crtend.o crtn.o
编译的过程: 编译器 从源代码(Source Code)到最终目标代码(Final Target Code)过程,做了以下 6 个步骤
词法分析
通过扫描器,运用一种类似于 有限状态机(Final State Machine) 的算法,将源代码的字符序列分割成一系列的 记号(Token)
词法分析产生的记号一般分为以下几类:关键字、标识符、字面量(包含数字、字符串等)和 特殊符号(如加号、等号),并将它们放到对应的 表 中
lex 程序可以实现词法分析,支持自定义词法规则
语法分析
通过 语法分析器(Grammar Parser),采用 上下文无关语法(Context-free Grammar) 的分析手段,将由扫描器产生的记号生成 语法树(Syntax Tree)
语法树就是以 表达式(Expression) 为节点的树,如果出现了表达式不合法,编译器就会报告语法分析阶段的错误
yacc 程序可以实现语法分析,构建一颗语法树,支持自定义语法规则
语义分析
语义分析器(Semantic Analyzer) 只对表达式完成语法层面的分析,不关心是否真正有意义,通常可以分为以下两种
静态语义(Static Semantic) 通常包括声明和类型的匹配,类型的转换,最简单的例子:赋值类型转换
动态语义(Dynamic Semantic) 一般指在运行期出现的语义相关问题,比如将0作为除数
经过语义分析之后,整个语法树的表达式都被标识了 类型,如果有些类型需要做隐式转换,语义分析程序会在语法树中 插入 相应的转换节点
中间语言生成
现代的编译器都包含 源码级优化器(Source Code Optimizer),在源代码级别有一个优化过程(比如能在编译期间确定表达式值)
直接在语法树上做优化比较困难,所以将整个语法树转换成 中间代码(Intermediate Code),比较常见的有:三地址码(Three-address Code) 和 P-代码(P-Code)
跨平台原理:中间代码使得编译器可以被分为前端和后端。编译器前端负责产生机器无关的中间代码,编译器后端将中间代码转换成目标机器代码。这样对于一些可以跨平台的编译器而言,他们可以针对不同的平台使用同一个前端和针对不同机器平台的数个后端
目标代码生成与优化
源代码级优化器产出的结果:中间代码,标志着下面的过程都属于编译器后端,包括 代码生成器(Code Generator) 和 目标代码优化器(Target Code Optimizer)
代码生成器(Code Generator) 主要是将中间代码转换成目标机器代码,依赖于目标机器的环境
目标代码优化器(Target Code Optimizer) 主要针对目标代码,在合适的寻址方式,使用位移来代替乘法运算,删除多余的指令等方面,进行优化,涉及到的技术比如: 基址比例变址寻址(Base Index Scale Addressing)
问题:此时,目标机器代码(汇编)已经生成,但是目标代码中的变量地址还没有确定,这个地址可以是本程序内定义的,也可以是其他程序模块中的,那么运行时该如何寻址?在介绍链接器之前,先说明下链接器的作用对象:目标文件都包括什么