从代码到程序的主要步骤如下:
- 预编译
- 编译
- 汇编
- 链接
1 预编译
预编译的功能主要是读取源程序,对源程序当中的伪指令和特殊符号进行处理,包括
- 宏指令 #define Name TokenString, 将Name用TokenString替换
- 条件编译指令 #ifdef
- 头文件包括指令 #include
- 特殊符号 LINE标识符行号、FILE源程序名称、注释
经过预编译处理,获得一个和没有经过预处理的源文件含义相同但内容不同的输出文件,文件里没有宏指令、条件编译指令、头文件包括指令和其他特殊符号,这个文件将作为编译的输入
2 编译
宏观上讲,编译是将一种高级语言书写的程序转化为低级语言书写的程序,此处指将c语言编写的程序转化为汇编程序,编译过程又可以细分为:
- 词法分析
- 语法分析
- 语义分析
- 中间代码生成
- 中间代码优化
- 目标代码生成
上述过程是一个经典的编译处理模式,并不是所有的编译程序都分为上面这几个阶段,有的编译过程不需要生成中间代码,有的编译也不会进行优化
- 解释程序和编译程序的不同
- 解释程序边翻译边执行,最后得到程序运行结果,并不会生成目标代码
- 编译程序只将源程序翻译成目标程序,输出的是和源程序等价的目标程序
2.1 词法分析
词法分析任务是从左到右一个字符一个字符读入经过预处理的源程序,对构成源程序的字符进行扫描和分解,识别出一个个单词。
单词:指逻辑上紧密相连的一组字符,具有集体含义,比如关键字、算符、界符、常量等等
词法分析程序输出的单词符号可以采用以下二元式表示 (单词类别,单词自身的值)
2.2 语法分析
语法分析在词法分析的基础上将单词序列分解为各类语法短语,比如语句、表达式。
语法分析依据语言的语法规则,即描述程序结构的规则,通过语法分析可以确定整个输入串是否构成一个语法上正确的程序
语法分析又可以分为自顶向下分析和自底向上分析
-
自顶向下分析:从文法的开始符号出发,考虑如何根据当前的输入符号唯一确定使用哪个产生式替换相应的非终结符以往下推导,比如LL分析法,简单来说,是从开始符号出发,看看是否能够得到目标字符串
-
自底向上分析:每一步从当前句型中选择一个可规约的子串,将它规约到某个非终结符号,比如算符优先分析法、LR分析法
2.3 语义分析
语义分析审查源程序有无语义错误,为代码生成阶段收集类型信息。比如检查每个运算符是否具备正确的运算对象;比如将运算符的运算对象做强制类型转换
2.4 中间代码生成
中间代码:结构简单、含义明确的记号系统,这种记号系统可以设置为多种多样的形式,设计原则有两点,一个是容易生成,一个是容易将它翻译成目标代码,很多编译程序采用了近似三地址指令的四元式中间代码,这种四元式的形式为 (运算符 , 运算对象1 , 运算对象2 ,结果)
- 中间代码的作用
- 用于源语言和目标语言之间的桥梁,避开二者之间较大的语义跨度,让编译程序的逻辑结构更加简单明确
- 利于编译程序的重定向
- 有利于进行与目标无关的优化
2.5 代码优化
代码优化是为了对前面阶段产生的中间代码进行变化或改造,目的是让生成的代码更加高效,省时间和省空间
- 代码优化的种类
- 局部优化
- 删除公共子表式
- 合并已知量
- 复写传播
- 删除无用赋值
- 循环优化
- 代码外提
- 强度削弱
- 变换循环控制条件
- 全局优化
- 局部优化
2.6 目标代码生成
目标代码生成任务是为了将中间代码换成特定机器上的绝对指令代码或可以重定位的指令代码或汇编质量代码
3 汇编
汇编的作用是将得到的汇编代码翻译成机器指令,只是做翻译工作,得到obj文件
4 链接
链接是将不同模块的源文件在之前过程得到的不同的目标文件链接得到可执行文件,也就是我们的可以运行的程序的过程,链接又分为静态链接和动态链接
- 静态链接:在程序运行前将各个目标模块和其需要的库函数链接成一个完整的装配模块,以后不再拆开。这种先进行链接所形成的一个完整的装入模块,又称为可执行文件。通常不再把它拆开,需要运行的时候将它装入内存
- 动态链接:编译后得到一组目标模块,装入内存时,采用边装入边链接的方式。比如要调用的时候找到相应的外部调用模块,装入内存,
- 便于修改和更新
- 便于实现对目标模块的共享