西北工业大学 编译原理实验 minic文法 编译器前端 flex-bison实现 的 debug手记

这是上学期写的东西了,debug那里写得过于简单了,其实我找了四五天bug,有过好多,来不及记下来。现在仅供参考。(2022.1.9)


去年调了龙芯杯,今年调编译器,马上就要ddl了,我感觉我差不多要死了。
什么才算独当一面呢?我什么时候才能成为独当一面的忍者呢?

一、做编译器前端的过程

老师的要求是:生成ast还有四元式(线性IR)

AST

首先我就不知道ast该咋画,我基本找了全网有图的所有ast,排列如下(大家不用费心继续找了,因为网上的图都一个样,就是全不全的问题 ):

flex-bison工具

这个工具的使用

实现思路

结构体定义

一些知识

遇到很多问题,C语言边用边查(大一的时候就没记住什么库函数——因为我忘性实在是太好了)

sprintf

对于将字符串和数字合并,我扒拉了全网,翻了一些书,总结了一些方法

char*

字符串数组使用时应该注意什么呢?我也总结了一下。

Makefile相关

这个还有一些零碎的知识(我问的老师,算是一些小台阶),看这篇文章。

graphiz工具使用方法

同志们,f ge q吧,wai net资料真的良心
文章一、关键词:
文章二、关键词:
文章三、关键词:
很久以后(在程序宕掉多次之后)我才知道,如何打印特殊字符“+ - * /”,就是把它们用双引号引起来

某些冲突的解决

朋友,你见过minus和nminus的写法吗?

二、debug过程

从第n个bug开始(n>5),有时间的话再补之前的

第n个bug

现象

思考

解决过程和结果

现在是6月20号的晚上九点半
第一张能正经地画出来的图:
在这里插入图片描述

第n+1个bug

现象

可以看到上面那个图不怎么样:

  1. 我的函数里对于符号这个东西,T_IDENT归做一类,序号递增,但是此处看起来就及其别扭(main和后面的abc甚至是赋值语句里的a都有增长序号,但是没有区分)
  2. 原本Localdef的地方写的是global_define
  3. 还有那个array_1,那个位置本该是赋值语句,没有等号就算了,没有数字也就算了,咋还多个array?

但是还是要夸一下:基本框架和父子关系倒是出来了;而且可视化成功对于之后的调试会很有帮助;而且我学到了一个调试的小方法——bug砸下来,我就是gdb!(手工产生式推导)

解决

大早晨起来改了改代码,它就给我宕掉了。改的是打印树的逻辑。查找bug的时候添加了观察点,对于程序的pre-mid-aft都有分隔,对于每个if分支语句都如此,这样的结果是找到了最终宕掉的地方以及bug所在。
在这里插入图片描述
另外,关于那个莫名的factor和array,在产生式中采用输出的方式发现本会出现那个东西的位置并没有走过,所以猜测不是产生式走到不该走的地方,而是输出的时候有问题。忘记当时怎么造的table表了,但是对比了一下table和enum中的各项,发现少了一个!错位了!就这样,程序居然还能运行,我也是醉了!(我是不是可以靠写bug,然后debug,然后给大家观赏我的脑残过程娱乐大家来赚钱呢?
在这里插入图片描述在这里插入图片描述

第n+2个bug

对于测试代码

int main()
{
    int a,b,c;
    a = 3;
    b = 3+4;
}

在这里插入图片描述

可以看出,虽然array改了,但是,那个factor还是让人不太舒服,不过现在已经不怎么影响强迫症了,强迫症现在要解决的是,这玩意儿咋不按照预定的输出?输出我的赋值变量呢?

解决

找到原因了,其实不算bug,只是输出有点问题
在这里插入图片描述
错误是onevone_replace(),每次都是简单地改一下标签,然后就把叶结点和根节点替换掉,设计思想本来是想着ast有抽象的重任,要把那些乱七八糟的中间文法改造带来的乱七八糟的表签屏蔽掉,但是该屏蔽的屏蔽了,不该屏蔽的也被我屏蔽掉了。
另外补充一点,这个函数,出过不下三次错了,每次程序都因为这个出一些莫名的错,这除了和这个函数应用广泛有关外还和这个函数本身考虑不够全面有关——一个本该适用广泛的函数却泛化能力不强,灾难就来了。
这其实也有点软件工程那个二八定律的意思了,百分之八十的错,来自这百分之二十的代码。
现在的它被我改为了:

struct ast_node* onevone_replace(struct ast_node* node, struct ast_node* node1, int type, char* name)
{
    if(node1 != nullptr){
        node = node1;
        if(node1->ast_type != NUM && node1->ast_type != REAL){
            node->ast_type = type;
            node->ast_name = name;
        }        
    }
    else{
        node = nullptr;
    }
    return node;
}

这样一种形式。其实,这样模块简直是软件工程中的偶然内聚,最不提倡的那种!希望我的软工老师不要看到我写的这个代码。

第n+3个bug

起因

顺手修了几个小bug,关于输出yacc和lex中的自带的那些变量,.dot文件不支持±*/=这些玩意儿,所以我改了一波。
在这里插入图片描述
正当我自鸣得意,觉得这些东西都正常输出了的时候,我发现,那个global又出来了,翻看前一张图片,也是有这个global的,也就是加了global_arraytail,不错位了之后,本该在这里的东西就出现了(妖孽!现出原形吧!

分析

  1. 这意味着:我一开始对产生式的理解有点问题,我要看看写着global的地方是不是global的
  2. 你看着这个树,它够抽象吗?

我想,我应该解决两个问题:

  1. 产生式global不global
  2. 多进行几个测试用例的测试,总体来看,产生的树的情况,然后总体来改,那个抽象不抽象的问题

解决

首先,把第一个功能点的图展示一下:
在这里插入图片描述
我们可以看到,应该把assignstat的子语句中的factor去掉;global_paradata其实也该去掉,这算一个没用的中间层。(为什么会有这么多没用的中间层!

另外,我突然想到,应该把LOP和ROP都拆开,写成++和–的形式,这样才好显示语法树,可能写四元式的时候也会方便些。
还有那么多测试用例没有过……包括我觉得观感最不好的if-else语句,不晓得之后还需要多久。

  1. 对于global的去除:

  2. 对于factor的去除:

<asstail> → '='  <assexpr>  <asstail> | ε
<orexpr> → <andexpr> <ortail>
<ortail> → '||' <andexpr> <ortail> | ε
<andexpr> → <cmpexpr> <andtail>
<andtail> → '&&' <cmpexpr> <andtail> |  ε
<cmpexpr> → <aloexpr> <cmptail>
<cmptail> → <cmps> <aloexpr> <cmptail>| ε
<cmps> → '>=' | '>' | '<' | '<=' | '==' | '!='
<aloexpr> → <item> <alotail>
<alotail> → <addsub> <item> <alotail> | ε
<addsub> → '+' | '-'
<item> → <factor> <itemtail>
<itemtail> → <muldiv> <factor> <itemtail> | ε
<muldiv> → '*' | '/'
<factor> → <lop> <factor> | <val>

这段涉及到一连串的加减和逻辑运算,本来想着这些会比较难,看起来应该不舒服,也不容易看懂,但是我突然抓住了几个关键点:
从assignstat出发,到factor附近找错即可对于类似于以下的结构,那个xxxtail其实是不用管的,因为要是有它,其实就得出现||之类的东西

<orexpr> → <andexpr> <ortail>
<ortail> → '||' <andexpr> <ortail> | ε

于是我一路找到我写的这段代码:

Factor : //表达式啊表达式,分优先级的表达式,此处为最高的一级
        MINUS Factor %prec UMINUS{
            $$ = alloc_astnode(MINUS, strdup($1));
            $$->son_node[0] = $2;
            $2->parent = $$;
        } 
        | LOP Factor{
            $$ = alloc_astnode(LOP, strdup($1));
            $$->son_node[0] = $2;
            $2->parent = $$;
        } 
        | Val {
            $$ = onevone_replace($$, $1, FACTOR, nullptr);
        }
        ;     

看完我乐了,好嘛,又是onevone_replace()小宝贝!让我来用科学的方法评估一下,会不会按下葫芦浮起瓢,看到上面抽象语法树里,factor这些产生式明显都有些问题,但是问题顶多有两类:一类是它本身的问题,一类是它给它的父亲们造成的问题。
我想到了早上学的软件工程,它说,尽量改动最小化,对于这个问题,要想最小化改动,我想只有把Factor->Val那条式子改下了(这样不会影响到全局的问题):

| Val {
       $$ = $1;
   }    

首先看下这样改的结果对不对,再回头审视一下上一个bug可否这样改动(这样就影响最小了)。
结果矛盾转移了,发现还是上一个bug没改好,其实应该把关于Elem的产生式中的动作改一下的。
在这里插入图片描述
修改结果如下:

Val : //方括号指的是可扩展项,但是我总感觉它和{}除了递归定义就没区别了
        Elem {
            $$ = $1;
        }

在这里插入图片描述
555,这个ast好清爽!虽然我感觉expr那个还是一个阴影,不过要是这种表达式之后统一是expr了的话也算很好的结果了——这样可能对线性IR产生就有好处了。
该吃晚饭了!
等会儿边复习软工边过完功能点看看,该思考符号表的事情了(还有关于“一个节点的名称和属性不是同一个”的问题,看看对应的可以怎么解决。)

三、推荐书目

《flex与bison中文版》在这里插入图片描述
《Makefile》(从github上下载)
在这里插入图片描述
《编译原理(龙书第2版)》
《编译原理及实践》


四、后记:悟道

  1. 武功是什么样子,对不懂的人来说,大概就像看到武侠片里那种打斗场景,嘿吼一下,就像放了个二踢脚一般的特效,课本不救小白,实践操作书里其实也不针对小白,小白是连数据结构的定义刚开始都模模糊糊的那种白。所以我才想写博客,记录一下每段历程,从小白到不那么白的过程,也算留下了点什么。
  2. 从大一开始,我就对自己的能力和对自己的代码有着深深的不信任感,不敢尝试,不敢犯错,但是这种感觉近一年渐渐收束了,这不仅是能力提升,还有心态提升,我更加敢于坚定地推进自己的工作,遇到bug找bug,而不是畏手畏脚,遇到困难就想撤退换路。大概是更加从容和无畏了。(这种从容和无畏大概来源于学分绩正好离门槛差一点,正好我又想考研,所以那种过分在意就没了
  3. 如果你感觉眼大漏光找不到错误,你可能需要减小字体,使每页为25行左右代码。
  4. 可视化对于调代码真的超级有用!越明显的可视化越好!

最后,要吹爆zlj老师,太负责任了,不论是做的过程的指导,还是检查的时候的认真,还有心慈手软 ……
记得有天我把问题发到实验群里,老师立刻一个电话过来问我问题是啥,虽然上别的课摸鱼的我(欠打)给老师挂了,但是后来早晨八点老师给我讲了一个多小时,不管我多菜老师都没放弃我。

  • 2
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值