编译链接过程(二)

前一篇博文提到编译的几个步骤,这一篇来了解下具体每一步都干了些什么,好叫心里有数。详细的过程,我想只有通过分析一个具体的编译器代码才好。下面介绍的几个步骤完成了源码文件经过编译链接后成为可执行文件:

1 预编译


  •    以C语言为例,C语言的.c文件中包括include,define等等,这些依靠C语言的预处理器(cpp)来进行处理的。
  •    将代码包含的#define删除,并在代码中用到宏定义的地方扩展开来;
  •    条件编译,如#if,#ifdef,#elif,#endif等语法进行处理;
  •    删除程序的注释;
  •    将头文件#include加进来,插入到该指令所在位置。

2 编译


预编译后的文件,不再包含注释,头文件也插入进来,条件编译也得到相应的处理。那么,剩下的就是实实在在的源码,需要经过词法分析,语法分析,语义分析及优化,然后产生汇编代码。下面结合具体的代码实例讲解这些过程:
array[index] = (index + 4) * (2 + 6)

    2.1 词法分析


首先,扫描器(Scanner)会扫描预处理后的源码文件,它的作用就是将源码中的一个个字符,划分为符合词法规则的不同类别,称为记号(Tokens)。这些Tokens是后面步骤处理的最小单位,每一类的记号具有特别的意义。一般Tokens的类别有:关键字,标识符,字面量(数字和字符串)和特殊符号(如加号,乘号等)。结合后面介绍的语法规则,每一类都有不同处理方法。扫描的结果是将这28个非空字符,划分为16个记号,并且为每个记号归类:
  • array --->  标识符
  • [     --->  左方括号
  • index --->  标识符
  • ]     --->  右方括号
  • =     --->  赋值
  • (     --->  左圆括号
  • index --->  标识符
  • +     --->  加号
  • 4     --->  数字
  • )     --->  右圆括号
  • *     --->  乘号
  • (     --->  左圆括号
  • 2     --->  数字
  • +     --->  加号
  • 6     --->  数字
  • )    --->  右圆括号

这些记号必须符合一定规则,例如一般程序语言都有算术运算,那么符号+,-,*,/等就是合法的记号,同时可能还有运算符重载,既一个符号同时承载了两种及以上的意义。C语言规定标识符必须是数字,字母或者下划线组成,且首字母不能为数字。


    2.2 语法分析


语法分析器在上步的基础上,通过Tokens的类型,判断是否符合语法规则。语法分析多采用上下文无关语法,最终生成语法树。上下文无关语法规则描述了程序语言的语法。一般包括一个终止符的有限集,一个非终止符的有限集,一个产生规则的有限集和一个非终止符的开始集。通常程序语言的语法规则通过巴克斯-诺尔范式(BNF)表示。凡是能够通过巴克斯-诺尔范式推到出的都是符合语法规则的表达式。通过语法分析后,程序的形式变为语法树,通过树的数据结构更能表达出程序的本质,也便于进一步处理,而不再是便于人类阅读的形式了。


    2.3 语义分析


语法分析后生成抽象语法树,能够发现程序的语法层面的错误,但是每个表达式也即语法树的节点的意义是什么,该语法树并没有交代。这时,通过语义分析,能够发现表达式及子表达式是否类型匹配,进一步把程序向精确化转化。这里提到类型匹配,每一种语言都必须给出程序的基本类型和组合类型,程序的变量,函数或者表达式也必须有类型。每一门语言的类型系统就是定义变量和表达式的类型的子系统。对类型的不同划分是各门语言的特点所在。

    2.4 中间语言生成

编译器为了对源码程序进行优化,会将上一步生成的语法树转化成中间代码表示,有三地址码和P代码两种方式。在一些功能强大的编译器中,它能对不同语言进行编译,目标代码可以运行在多种平台上,就是利用了中间语言来表示,从而简化编译器设计。上面的代码用三地址码表示:

  • t1 = 2 + 6
  • t2 = index + 4
  • t3 = t2 * t1
  • array[index] = t3

经过优化后可表示:

  • t2 = index + 4
  • t2 = t2 * 8
  • array[index] = t2

只用到一个寄存器器和一个内存就能完成上述表达式的运算。将程序转化成中间语言表示,后续的处理过程属于编译器后端。

    2.5 目标代码生成和优化

代码生成器将中间语言表示的程序,用对应的汇编表示出来,这里有个重要的主题就是寄存器分配,如何高效的利用寄存器来完成代码转化。每个CPU生产厂家的汇编代码不同,因此需要针对特点的汇编生成不同的目标代码。根据源码优化后的中间代码生成汇编代码:

  • movl index, %ecx                       ; value of index to ecx
  • addl  $4, %ecx                            ; ecx = ecx + 4
  • mull  $8, %ecx                            ; ecx = ecx * 8
  • movl index, $eax                        ; value of index to eax
  • movl %ecx, array(, eax, 4)         ; array[index] = ecx

目标代码优化器是在目标代码的基础上进行代码优化,比如选择合适的寻址方式,删除多余的指令等等。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值