![246422066ac3039a5c26751459b3eb2d.gif](https://i-blog.csdnimg.cn/blog_migrate/9a7b3eefd31990e913b04f186517076e.gif)
编
译
原
理
![3bb9ede6412f072a313325ffd8f5aa9a.gif](https://i-blog.csdnimg.cn/blog_migrate/1a5b665c83f606b31c389d2e2f765d53.gif)
课/程/设/计/攻/略/
![8470383f368af7661b75a09ded5cd566.png](https://i-blog.csdnimg.cn/blog_migrate/d7d0a24c52127fbbc4a797617c401d18.png)
编译器课程设计这门课程,说到底需要做的事情,是把符合文法的源程序翻译成正确的、目标机能懂的代码。编译器的实现方法和技术相对比较经典和成熟,理论课上各部分都有介绍,同学们需要做的是在理解这些方法的基础上,用自己的方式实现一遍。这门课常常给人一种很难的印象,主要原因是细节比较多,但只要静下心来按照老师指导的步骤,读PASCAL-S源代码、理解透文法、做好设计,每个环节都真正落实,就能做到事半功倍。如果选择生成抽象机上的PCODE,完全可以借鉴教材上介绍的PASCASL-S编译器采用一遍扫描的方式来完成。对于选择生成MIPS的同学,则需要花更多的工夫用于中间代码代码的设计、中间代码到MIPS汇编的转换,以及代码优化的算法实现。同学们在实现时,只要足够细心,跟着作业的节奏按部就班就能完成并无懈可击地通过测试。
代码未动,设计先行
1
任何一个项目必离不开充分的设计,编译器更是如此。
编译器的总体结构、符号表、中间代码、优化算法等无一不需要设计。比如,贯穿编译过程的符号表,其设计就非常重要。源代码中出现的名字存到一个表中还是按类别存到多个不同的表中?符号表中所存符号都要保存哪些信息?面对这些问题,我无法给出每位同学最适合他的建议,因为相信阅读了Pascal编译器代码的同学都会发现,尝试着在代码中理解他人的符号表各项属性的意义是一件很痛苦的事情。唯一的一点经验之谈便是不要将源代码到中间代码的翻译过程和中间代码到目标代码的翻译过程完全割裂为两部分,要都考虑清楚后再进行coding。前一步是插入符号表的过程,而后一步需要查表时,发现无法准确找到自己要找的符号,或是需要的信息没有保存,回过头再修改工程量是十分巨大的,且十有八九会出现纰漏。符号表能做到“信息全面”足矣,我们可以按照常量、变量、数组、函数分列具有不同属性的符号表,甚至细分为全局变量、局部变量等;我们也可以极度精简,取常量、变量、数组、函数他们中需要保存属性个数的最大值为符号表所存符号要保存信息的数量,然后将所有的符号都保存在一张表中。只要在查找符号的时候,能够准确地找到正确的符号并提取出所需的信息项即可。
作为衔接前端和后端的中间代码,其重要性也是显而易见的。中间代码到底如何设计?也有过一些同学来问我说学长到底怎么设计中间代码呀?你的中间代码可不可以给我看一看呀?其实,中间代码的设计老师已经给出建议,就是四元式“op des src1 src2”形式即可(当然有的时候可能只有一个操作数或没有操作数),至于op如何选择就真的因人而异了。大家目前的进度应该也早就超过了中间代码设计阶段,本文也就不再赘述。
如何面对看似de完的bug
2
最近一周,非常非常多的同学来问我说:“学长我把能想到的都测了,可还是有bug能否帮我看下什么地方有问题啊…”。可是真的每位同学都做到按照语法规则进行分支全覆盖的测试了么?(成环的情况可以只测两层呀)这里建议同学们可以按照读语句、写语句、赋值语句、条件语句等,分别构造测试样例,对每条语句全覆盖,工作量就小了很多。尤其需要重点测试的是语法成分,嵌套层数增加后是否还能正确地在内存中寻址呢?希望大家能够逐渐锻炼自己精心构造测试样例进行系统的测试,而不是一味地依赖拿看起来很复杂的长样例进行轰炸,企图碰到那个自己还没发现的bug。
另外,有的同学反馈,在本地能正确运行的代码,为什么在教学平台上没法通过测试。这类问题多半是由于同学的代码中使用了一些没有明确定义的语法成分,导致在不同环境下可能出现不同运行结果。比如,声明的变量未初始化、在一个语句中使用诸如++这类操作符的变量的多次出现等。为了帮助大家尽早修复这类问题,课程组开放了与评测环境一致的调试环境。另外,需要提醒大家,对于编译器报出来的warning,大家也需要逐一排除,保持代码无waring也是一种良好的编程习惯。
即将到来的优化
3
其实代码生成作业已经即将成为过去时,最新发布的竞速排序才是紧张刺激的新游戏。这个主要考察优化效果的作业,因为已经给出了测试样例,所以大家可以充分发挥自己的智慧。但还是建议同学们,要先进行普适的、大块的优化,比如寄存器分配、常量传递、死代码删除等,而不是着眼于针对特殊样例的优化。在进行优化的同时,一定要以代码正确为第一原则,不要为了简化MIPS指令而导致运行结果错误,得不偿失。
在代码优化阶段,书中提到的优化方案有很多,包括基本块内部的公共子表达式删除(DAG图)、全局寄存器分配(引用计数或着色算法)、数据流分析(通过活跃变量分析,或利用定义-使用链建网等方法建立冲突图)等。而各个方法的思想在书中也都很详细的阐述了一遍。这里就不在赘述了。
最后的最后,提前恭喜各位同学已经走过了大半的编译器设计,望各位同学再接再厉,在代码优化的竞赛中一展身手~
【END】
文字 | 张哲维
排版 | 布榆
![e140293b25b228f8a8bf0decdcee55b8.gif](https://i-blog.csdnimg.cn/blog_migrate/cda9d9b93fbe9983a43bfd325b58b105.gif)