类C语言编译器设计、源码及资料汇编(二)

2 篇文章 0 订阅
2 篇文章 0 订阅

3.语法分析器

语法分析器是整个环节比较主要的一环,大部分的任务都是由其承担。在前面的基础知识提到过语法分析的基本方法,由于递归子程序手工构造比较简便,故本设计使用的是递归子程序法。由于在实现递归时,要考虑是否有递归的出口,因此需要提取左公共因子,降低递归深度。在语法分析的时候,不仅需要考虑到语法分析程序的高效性和简洁性,在降低耦合性的同时需要注意函数的之间的联系。

语法分析时,可能文法的描述很简便,但不利于实现,需要对其文法上的等价变换才能更好地编写。这里将介绍函数逻辑和文法等价上的变换,介绍的书籍已经有很清楚的介绍,不再进行赘述。下面开始该过程实现之路。

3.1解析语句

Via-C语言由于有很多语句种类,下面介绍几种常见语句的解析过程。

例如for循环语句解析过程如下图3.1:

 

                                                                3.1  for 循环语句的解析过程

从括号解析,进行循环变量的初始化,再进行for循环的条件的判断,然后进行for循环体的代码的运行。

选择语句,这个都比较熟悉。下图3.2是语句的解析过程:

 

                                                               3.2 选择语句的解析过程

虽然跳转语句有 continue语句等好几种语句,但是它们解析的过程基本相似。解析过程如图3.3。

 

                                                                3.3 跳转语句的解析过程

 

语句还有其他几种分类,这里不再详细介绍。语句的解析关系着整个语法分析器的逻辑部分,具体可见文件grammar.c。

3.2解析定义和声明

解析类型符过程如图3.4:

 

                                                   3.4 类型解析过程

先经过词法分析器的解析后,对传递进来的标识符进行识别,上图解析了Via-C语言的所有的数据类型。

图3.5是声明符的解析过程:

 

                                                  3.5  声明符解析

声明符主要是解析函数定义和变量的定义,实现详情见grammar.c文件。

3.3解析表达式

Via-C表达式有好几类,虽然有在表现形式上有区别,但是表达式在解析过程中基本上是差不多的结构。另外解析表达式时,还需要考虑到运算符的优先级高低。在前面的表格中已经列出了优先级高低。图3.6是乘除表达式的解析过程。

 

                                                        3.6 乘除表达式解析过程

3.4语法缩进

在语法的分析中,有些程序没有进行缩进的处理,可能需要对其缩进,以便让  语法分析解析后产生的输出,更加直观。

以下是对语法状态的枚举类型定义

enum e_SynTaxState
{

      SNTX_NUL,                           //空状态

      SNTX_SP,                                //空格

      SNTX_LF_HT,                        //换行缩进

      SNTX_DELAY                       //延迟输出
};

具体的缩进操作函数和缩进动作实现见grammar.c,另外在Via-C中采用的缩进是4个空格。

3.5语法分析的测试

输入程序(图3.7)没有缩进,经过语法分析之后,得到结果(见图4.13),可以看到程序已经根据了声明和定义、函数定义等规则进行了缩进,证明了语法分析程序已经成功。

                                               3.7 语法分析的示例程序

可以看到示例程序是杂乱无章的,经过语法分析器分析后输出整齐一致,如图3.8。

                                                                3.8 语法分析结果图

4.符号表和语义分析器

源代码经过上一层次分析后,只能说明代码在语法和词法上符合相关的规则,并不能说代码已经是正确。例如在函数内部并没有循环结构,出现了非法的跳转语句,这是符合语法规则的,然而在逻辑上这并不是正确的。语义分析程序需要抓住这类的漏网之鱼。这一阶段的工作比较繁杂,需要先做好符号表的准备,再进行语义上的剖析,以便下阶段的代码生成准备。

4.1符号表

符号表主要是储存符号的相关特征信息,其需要在编译的过程不断收集信息的同时不断更新信息,通过对属性信息的查询可以进行语义检查。

符号表主要用栈来实现。由于变量存在作用域的区别,因此需要两个栈分别进行信息的收集。分别定义如下:

Stack G_Sym;   //全局符号栈

Stack L_Sym;   //局部符号栈

接下来编写符号表的操作函数,此后就可以在上阶段的基础上进行符号表的构造。

在构造字符表时需要注意的是字节对齐,尤其在自定义类型的成员上。字节对齐可以提高访问速度,但是如果一味地追求字节上的对齐,会导致许多空间没有储存数据,导致性能上的浪费。

4.2语义分析器

前面提到过静态缺陷和运行异常,语义分析器就是处理静态语义缺陷。语义分析器主要是限制目标程序和源程序在逻辑结构上完全一致,因此在以前语法分析器的基础上进行语义上限制。对语义的分析有两个层次,首先是正确性分析,即需要根据语言的定义规则去判断代码语义的正确性,其次是优化分析,也就是用于分析提高目的代码执行的性能。

语义分析器基本是在前面的基础上实现,实现详情见grammar.c文件。

5.代码生成器和错误处理

本设计的适用平台为Windows,所以可执行文件的格式为COFF,采用的机器语言也是Intel x86机器语言。Intel x86机器语言有七种指令,跳转和算术指令等等,以及各种的寄存空间的操作。代码生成器需要生成对应的指令和对应的寄存器的操作。具体函数见gencode.c文件。

代码生成器效果图如5.1所示,这一阶段任务主要是生成了中间代码文件。中间代码文件中储存了程序的相对地址,生成可运行程序需要链接器和库文件进行链接。下图展示了生成的效果。

 

                                                    5.1 中间代码文件生成

在编写代码的过程中,思维上的遗漏或者是语法上不正确的情形不可避免会出现,因此错误控制程序是有必要存在的。一般来说程序出错的等级有词法警告、语法错误以及运行异常等。

图5.2是Via-C的错误控制程序的处理流程,具体实现见error.c。

                                                         5.2 错误控制程序

6.链接器

前面已经介绍了链接器的有关信息。下面开始介绍链接器相关实现的信息。

首先介绍应用平台的有关知识。Windows平台使用的可执行文件为PE格式,而PE文件主要由DOS部分、NT头、节点表、节数据等部分构成,DOS部首由两部分组成:DOS的MZ文件标志和DOS存根程序,NT头包含了PE文件签名、COFF文件头、COFF可选文件头[8,16-19]。

这个阶段要加载相关文件和解析相关文件中引用的外部符号,并且计算RVA地址,然后进行目的文件的生成。

下面开始这阶段链接器逻辑的编写,流程顺序图见图5.3。

   

                                                              5.3 链接器的编写流程图

中间代码的优化,是可以在代码生成时采取优化措施直接进行优化,但这样仅仅是优化了一个文件,无法顾及到整个程序的优化。因此一些重要的优化就需要在链接器链接时才能进行。编译器有很多的优化方法,浅易如折叠不变量,高深同重排指令等等。但是有些要紧的优化是超过当今世面上编译器性能,例如使用高性能的算法替代低性能的算法,抑或是优化指令的排列,提高代码的运行性能。

图5.4是链接器的成果展示:

 

                                                   5.4链接器成果图

到这里已经完成了链接器的编写,同时编译内核的工作基本上完成了。后续将进入周边功能的实现。

 

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值