构建程序的过程可以分解为4个步骤,分别是预处理(Prepressing)编译(Compilation)汇编(Assembly)链接(Linking)

预编译

• 将所有的"#define"删除,并且展开所有宏定义
• 处理所有条件预编译指令,比如"#if"、"#ifdef"、"#elif"、"#else"、"#endif"
• 处理"#include"预编译指令,将被包含的文件插入到该预编译指令的位置
• 删除所有的注释"//"和"/* */"
• 添加行号和文件名标识,以便于编译时编译器产生调试用的行号信息及用于编译时产生编译错误或警告时能够显示行号。
• 保留所有的#pragma编译器指令,因为编译器须要使用它们
预编译后生成.i文件不包含任何宏定义

编译

编译器就是将高级语言翻译成机器语言的一个工具

词法分析

将源代码程序输入到扫描器,运用类似有限状态机的算法将源代码的字符序列分割成一系列的记号(关键字、标识符、字面量(包含数字、字符串等)和特殊符号(如加号、等号))以及将标识符存放到符号表,将数字、字符串常量存放到文字表等。
lex函数实现词法扫描

语法分析

采用上下文无关语法产生语法树(表达式为节点的树),在语法分析同时,主要做以下事情
很多运算符号的优先级和含义也被确定下来了。
对具有多重含义的符号进行区分,
如果出现了表达式不合法,比如各种括号不匹配、表达式中缺少操作符等,编译器就会报告语法分析阶段的错误。
语法分析工具名为yacc(Yet Another Compiler Compiler)

语义分析

语义分析由语义分析器完成。
编译器所能分析的语义是静态语义,即编译器可以确定的语义;动态语义是只有在运行期才能确定的语义。
静态语义通常包括声明和类型的匹配,类型的转换
动态语义一般指运行期出现的语义相关的问题,比如将0作为除数是一个运行期语义错误
经过语义分析之后,整个语法树都被标识了类型。
如果有些类型需要做隐式转换,语义分析程序会在语法树中插入相应的转换节点。

中间语言生成

这里可能会做些优化,源码级优化器会在源码级别进行优化。
源代码优化器将整个语法树转换成中间代码,它是语法树的顺序表示,其实已经非常接近目标代码了。但是它一般跟目标及其和运行时环境是无关的,比如它不包含数据的尺寸,变量地址和寄存器名字等。
中间代码有很多类型,不同编译器有不同的形式,比较常见的有:三地址码、P-代码。

目标代码生成与优化

汇编

汇编是将汇编代码转变成机器可以执行的指令,每一个汇编语句几乎都对应一条机器指令。所以汇编器的汇编过程相对于编译器来讲比较简单,它没有复杂的语法,也没有语义,也不需要指令优化,只是根据汇编指令和机器指令的对照表一一翻译就可以了。
经过预编译、编译、汇编直接输出目标文件。

链接

链接器历史

模块拼装-静态链接

将各个模块中相互引用的部分处理好,使各个模块之间正确地衔接。从原理上讲就是把一些指令对其他符号地址的引用加以修正。主要过程包括地址和空间分配、符号决议和重定位。

 

posted on 2019-08-28 08:30  AnotherICE  阅读(205)  评论(0编辑  收藏  举报