精华内容
下载资源
问答
  • 三地址语句的具体实现 三地址代码是由下面一般形式的语句构成的序列: x:=y op z 其中, x、y、z为名字、常数或编译时产生的临时变量;op代表运算符号。 每个语句的右边只能有一个运算符。 三元式 三元式主要由...

    三地址语句的具体实现

    三地址代码是由下面一般形式的语句构成的序列:

    • x:=y op z

    其中, x、y、z为名字、常数或编译时产生的临时变量;op代表运算符号。

    每个语句的右边只能有一个运算符。(有重复的)

    在这里插入图片描述

    三元式

    三元式主要由三部分组成:(有重复的)

    • (OP,arg1,arg2)
      在这里插入图片描述

    间接三元式

    为了便于优化处理,作为中间代码,设一张指示器表(间接码表)
    (没有重复的)????????
    在这里插入图片描述

    四元式

    四元式主要由四部分组成:(有重复的)

    • (OP,arg1,arg2,result)

    其中,OP是运算符,argl、arg2分别是第一和第二个运算对象,result是编译程序为存放中间运算结果而引进的变量,常称为临时变量。当OP是一目运算时,常常将运算对象定义为arg1。
    在这里插入图片描述
    注意:最后一行

    • 四元式出现的顺序和语法成份的计值顺序相一致。
    • 四元式之间的联系是通过临时变量实现的,这样易于调整和变动四元式。
    • 便于优化处理。
    展开全文
  • 三地址码转换控制流程图

    前言

    本文是对2. Intermediate Representation的整理。由于本科时候的《编译原理》学的稀烂,所以无法旁征博引。下面仅仅是将理解的课件内容搬运过来。详见课件对应的课程视频

    由于之前整理过clang&llvm简介。本文仅仅包含,三地址码(3-address code, 3AC)转换成控制流程图(control flow graph, CFG)。


    三地址码

    三地址码是语言的一种中间表示(intermediate representation, IR)。三地址码由类似于汇编语言的指令序列组成,每个指令最多有三个操作数(operand),并且通常是赋值和二元运算符的组合。例如,t1 := t2 + t3。(三地址代码拆分了多运算符算术表达式以及控制流语句的嵌套结构,所以适用于目标代码的生成和优化。)

    比如将下面的一条语句,转换成3AC形式。

    x = (-b + sqrt(b^2 - 4*a*c)) / (2*a)
    
    t1 := b * b
    t2 := 4 * a
    t3 := t2 * c
    t4 := t1 - t3
    t5 := sqrt(t4)
    t6 := 0 - b
    t7 := t5 + t6
    t8 := 2 * a
    t9 := t7 / t8
    x := t9
    

    常用的三地址指令,如下所示。其中

    指令指令含义注释
    x = y bop z二元操作bop: binary arithmetic or logical operation
    x = uop y一元操作uop: unary operation (minus, negation, casting)
    x = y赋值操作-
    goto L无条件跳转L: a label to represent a program location
    if x goto L / if x rop y goto L有条件跳转rop: relational operator (>, <, ==, >=, <=, etc.)

    这里略去:将真实代码转换成三地址码,解释三地址码指令含义


    控制流程图

    思考下,如何使用程序(算法),将下面的3AC转换成CFG。

    在这里插入图片描述

    基本块

    在转换之前,我们需要了解基本块的概念。基本块(basic block, BB)是连续的三地址码指令,这个块具有如下性质:

    • 必须从第一条指令,进入基本块。
    • 必须从最后一条指令,离开基本块。

    构建基本块的算法如下:

    输入:一个程序P生成的三地址码序列。
    输出:程序P的一系列基本块
    程序:
    	(1)找出程序中,将来生成的基本块的第一条指令(我们称之为leaders)。
    	* 程序P的三地址码的第一条指令,是leader。
    	* 所有非条件/条件跳转指令的目标位置指令,是leader。
    	* 所有非条件/条件跳转指令的下一条指令,是leader。
    	(2)生成程序P的基本块。
    	如果将程序P看出直线,leaders为不同的切割点。切割出来的(左闭右开)区间便是基本块。
    

    上面的示例三地址码程序,经过该算法,得到的基本块,如下图所示。

    在这里插入图片描述
    在这里插入图片描述

    基本块的连接

    基本块为节点。下面,我们只需要在基本块上添加真确的边,便可以生成控制流程图。连线遵循下面两条规则:

    • 基本块之间,存在非条件/条件跳转,需要添加边。
    • 顺序相邻的两个块之间,需要添加边。

    给基本块节点,添加边之后,如下图所示。即,将三地址码程序转换成控制流程图。

    在这里插入图片描述

    展开全文
  • 【多选题】高级语言翻译成机器语言的方式有( )两种。【单选题】下列货物中,( )可以按一批托运【单选题】铁路运输收入包括( )、货运收入、铁路建设基金和代收款【单选题】承运人按铁路有关规定运单,若符合运输条件,...

    【多选题】将高级语言翻译成机器语言的方式有( )两种。

    【单选题】下列货物中,( )可以按一批托运

    【单选题】铁路运输收入包括( )、货运收入、铁路建设基金和代收款

    【单选题】承运人按铁路有关规定运单,若符合运输条件,并在运单上签证货物搬入日期或装车日期,即为( )。

    【填空题】在车站公共场所以外的地方装卸货物,______由负责。

    【判断题】自动控制是在没有人直接 参与的情况下,利用外部装置使被控对象的某个参数(被控量)按 预订的规律运行 的要求变化。

    【单选题】承运人对一般货物的保管期限为 ( )

    【多选题】下列属于计算机性能指标的有( )

    【多选题】我国铁路货物运到期限由( )、( )、( )三部分组成

    【填空题】我国铁路货物运输品名分类,共分为26个大类,还另设有______和集装箱两个品类,共计28个大类。

    【填空题】标准ASCII码用()位二进制位来表示128个字符。

    【填空题】托运人提出货物运单后,经承运人审查,若符合运输条件,则在货物运单上签证货物搬入日期或装车日期的作业,称为 ______。

    【填空题】()是固定在微型计算机主板上的一块ROM芯片,其中存放了整个系统最基本的输入输出程序。

    【单选题】冻肉(2箱,25kg/件)可按( )办理

    【多选题】货场的配置图可分( )、( )以及( )

    【判断题】计算机的“运算速度”的含义是指每秒钟能运行多少条操作系统的指令。

    【单选题】按零担托运的货物,必须按( )承运

    【单选题】根轨迹法是一种( )

    【单选题】货票一式四联,其中( ) 是运输凭证

    【填空题】计算机工作时需要首先将程序读入()中,控制器按指令地从中取出指令(按地址顺序访问指令),然后分析指令,执行指令的功能。

    【多选题】适合集装箱运输的货物有( )

    【单选题】货运员对搬入货场的货物进行检查核对,确认符合运输要求并同意进入场库指定货位,叫( )

    【填空题】______ 应组织一站直达车装运

    【单选题】1 吨集装箱一般用 ( )装运

    【填空题】使用集装箱装运的货物,必须使用同一箱型,每批不得超过铁路___________所能装运的箱数。

    【单选题】已知某些系统的开环传递函数如下,属于最小相位系统的是( )

    【填空题】位于CPU与内存之间的一种容量较小速度较高的存储器是()。

    【填空题】货物 ______是指承运人在规定的地点与收货人进行货物(车)交接后,并在货物运单上加盖货物交付日期戳记,即表示货物运输过程终止的作业程序。

    【单选题】押运人数除特殊情况外,一般不超过( )

    【填空题】进货指______按签证的货物运单指定的日期,将货物搬入货场指定地点。

    【多选题】计算机的发展趋势是( )

    【单选题】( )集装箱可与普通零担货物混装一车

    【单选题】到站在收货人办完货物领取手续和缴清应交费用后,货物运单( )

    【填空题】货物在______ 所进行的各项货运作业,统称为货物的发送作业。

    【单选题】( )是衡量货车利用质量的综合指标

    【填空题】未来的计算机将向巨型化、() 、网络化和智能化的向发展。

    【单选题】系统在 r(t)= 作用下的稳态误差 ,说明 ( )

    【填空题】托运人托运货物,应向车站______提出货物运单一份。

    【填空题】一批是铁路办理货物承运的基本计算单位,一批货物就代表一份______ 。

    【单选题】在电气环节中,可直接在复域中推导出传递函数的概念是

    【填空题】托运易腐货物和短寿命放射性货物,其容许运到期限至少须大于货物运到期限______天。

    【判断题】线性定常系统的响应曲线仅取决于输入信号的种类 和系统的特性,与外输入信号施加的时间无关。

    【单选题】阶跃输入函数r(t)的定义是( )

    【填空题】货物运单是______该批货物的凭证,具有法律约束力。

    【填空题】货物实际运到日数,超过规定的运到期限时,承运人应按所收运费的______ ,向收货人支付一定数额的违约金。

    【多选题】货物运价按货物运输种类可以分为( )

    【多选题】集装箱箱体上的标记有( )

    【单选题】某货运站只办理煤的到达作业,该货运站为( )

    【填空题】铁路按______ 和重量承运货物

    【填空题】需要冷藏、保温或加温运输的货物不得按 ______ 办理。

    展开全文
  •   三地址码是一种平台无关的中间代码(类似汇编,但没到 x86、MIPS 那么具体),特点是:1、变量和 label 无需换具体的地址,能区分清楚就行(例如嵌套作用域的同名变量要区分开);2、寄存器无限量,不需要考虑...

      咕得有点久了
      这是编译原理大作业的第二步:进行语义分析,生成三地址码。
      三地址码是一种平台无关的中间代码(类似汇编,但没到 x86、MIPS 那么具体),特点是:1、变量和 label 无需换成具体的地址,能区分清楚就行(例如嵌套作用域的同名变量要区分开);2、寄存器无限量,不需要考虑有限的寄存器池;3、没有关于 CPU、操作系统的对接细节。这还是一个比较中间层次的东西,要生成具体的可执行代码时,不同平台可以直接拿三地址码来翻译。
      有了上一步的语法树之后,这一部分就不需要额外的工具了,就在语法树上完成需要的操作。尽管语义分析可以在语法分析的过程中完成,但是建出树以后再操作会好写一点,反正时间复杂度是不变的。
      参考书用龙书即可(Compilers Principles, Techniques, Tools. second version),龙书已经把框架给得很清楚了,虽然略去了一堆坑 b 的细节。。。
      三地址码的格式也用龙书的。

      项目地址

    语义分析

      语义分析是比较大的概念,对于不同的程序有不同的语义分析内容,例如,基于我的语言定义,可以进行包括但不限于如下的语义分析:

    • 检查整个程序是否有且仅有一个 MAIN 标识的函数;
    • 检查变量、函数引用前是否声明;
    • 检查变量、函数是否重定义;
    • 类型检查与类型强制转换。目前 Tiny+ 程序可用的类型只有 BOOL,CHAR,INT,REAL,它们之间都可以强制转换,且已按优先级从低到高排好。因此可以不用进行类型检查,只需在类型不匹配时强制类型转换(低优先级转换到高优先级);
    • 调用函数时检查参数及类型是否匹配;
    • 检查函数是否以 RETURN 结尾,若不是,需提出 warning。

      我们可以在生成三地址码的过程中顺便做这些事情。

    符号表

      符号表是一个栈结构,用于记录生成代码中遇到的各种符号,以便在真正的编译中替换成地址。本次的符号表需记录以下内容:

    • 定义的变量;
    • 定义的函数;
    • 控制流跳转用的 label

      具体实现,在基类 node 中定义符号表:(26 行)

    enum Symbol_t{
        SB_VAR,
        SB_FUNC,
        SB_LABEL,
        SB_NOTFOUND
    };
    
    class node;
    struct Symbol {
        Symbol_t type;
        string label;
        Var_t varType;
        node *ref;        // reference to that symbol
    
        Symbol(Symbol_t ty,string lb="",Var_t vt=V_BOOL,node *rf=nullptr) {
            type=ty, varType=vt, label=lb, ref=rf;
        }
        Symbol() {}
    };
    
    typedef unordered_map<string,Symbol> SymbolTabType;
    
    class node{
        public:
        ...
        static vector<SymbolTabType> symbolTab;         // symbol table. It is a stack
        static int EOCFCnt, elseCnt, loopCnt, varCnt;   // label count
    };
    

      用 std::vector 来模拟符号表的栈(为了可以遍历栈元素而不用 std::stack);栈的每一项是一个 unordered_map<string,Symbol>,用于映射符号到其信息;信息用 struct Symbol 来记录,内容包括符号类型、符号的 label、符号的运算类型(BOOL,INT 等)、代表该符号的语法树节点。
      在实际编译中,只有跨文件的符号需要记录 label 以用于链接,其余符号可直接记录其内存地址,因为符号最终就是要替换成地址的。
      另使用 ```EOCFCnt, elseCnt, loopCnt, varCnt` 四个计数器来生成控制流跳转 label 标号、else 跳转 label 标号、循环跳转 label 标号、变量及寄存器唯一标号。使用计数器的目的就是使得 label、变量和寄存器标号变得唯一。

    从符号表中查找符号

      从符号表中查找一个符号,就从栈顶往栈底依次查找,找到了返回相应的 Symbol 信息,找不到就返回 NOTFOUND

    Symbol find_symbol(string s) {
        for(int i=node::symbolTab.size()-1; i>=0; i--)
            if (node::symbolTab[i].count(s)) return node::symbolTab[i][s];
        return Symbol(SB_NOTFOUND);
    }
    

    控制流结束跳转 label

      If、For 等控制流需要一个继承属性 label 表示该控制流结束之后跳转到何处。这个继承属性在符号表中实现,用 %EOCF 符号(End Of Control Flow)来表示该语句结束后应跳转到的 label。这样 If、For 等节点具体生成代码的时候,只要从符号表中查找最近的 %EOCF,就知道如何跳转了。

    三地址码

      在基类 node 中定义虚函数 generate_3addr_code(),即每个节点类实现自己的三地址码生成过程。

    typedef vector<pair<string,string>> Codes;      // format: pair<label,instruction>
    
    class node{
    	public:	
    	...
    	Codes codes;
    	virtual bool generate_3addr_code() {}      // return 0 if compiled successfully
    };
    

      三地址码形如 vector<pair<string,string>>,每条指令代码用两个 string 表示,前一个 string 表示 label,后一个 string 表示指令。
      本次实验中,label 和指令是多对一的关系,即相同名称的 label 一定指向同一条指令,但一条指令可以对应多个 label。这在实际编译中也是可行的,因为 label 的本质是内存地址,如果用内存地址代替 label 的记录,那么 label 和指令就是一一对应的了。
      接下来分不同的节点类来说明 generate_3addr_code() 的实现,以及相应的语法检查。

    Program

      Program 是整个语法树的根,它新建一层符号表用于标记全局 label,调用它的子节点(MethodDecls)生成代码,并在整份代码开头补充一句 goto mainFuncLabel 使得程序跳到 MAIN 函数入口。

    bool node_Program::generate_3addr_code() {
        symbolTab.push_back(SymbolTabType());                   // global label
        bool err=son[0]->generate_3addr_code();
        codes.push_back(make_pair("","goto "+mainFuncLabel));   // goto main function
        add_codes(codes,son[0]->codes);
        symbolTab.pop_back();
        return err;
    }
    

      add_codes(a,b) 是把 b 的代码加到 a 的末尾。

    void add_codes(Codes &a,Codes &b) {
    	if (a.empty()) a=b;
    		else for(auto code:b) a.push_back(code);
    	b.clear();
    }
    

    MethodDecls

      该节点可以得到整个程序的函数列表。因此先把函数标识符添加到符号表中,生成它们的跳转 label("__" 加函数名),这样就可以做到函数的调用与声明顺序无关。
      此处顺便找出 MAIN 标识的函数,并检查是否唯一。

    string node::mainFuncLabel="NO";
    
    bool node_MethodDecls::generate_3addr_code() {
        bool err=0;
        // add function names to symbol table, and find 'main'
        for(auto xp:son) {
            node_MethodDecl *x=(node_MethodDecl*)xp;
            string &funcName=((node_Id*)(x->son[1]))->name;
            symbolTab.back()[funcName]=Symbol(SB_FUNC,"__"+funcName,((node_Type*)(x->son[0]))->varType,xp);
            if (x->isMain) {
                if (mainFuncLabel!="NO") semantic_error("more than one main function")
                    else mainFuncLabel="__"+funcName;
            }
        }
        if (mainFuncLabel=="NO") semantic_error("no main function");
    
        for(auto xp:son) {     // enumerate each method
            // each method generates its codes and add to this->codes
            node_MethodDecl *x=(node_MethodDecl*)xp;
    		err|=x->generate_3addr_code();
    		add_codes(codes,x->codes);
        }
        return err;
    }
    

    这里用到了语义报错操作。简单地在节点类里记录一下当前节点对应的代码的行号(新建节点时保存 flex 的 yylineno 即可),就可以报错时输出行号了。

    #define semantic_error(message) {\
    	cout << lineno << ": semantic error: " << message << endl;\
    	err=1;\
    }
    #define semantic_warning(message) {\
    	cout << lineno << ": warning: " << message << endl;\
    }
    

    MethodDecl

      该节点是具体的一个函数。首先新建一层符号表,表示新一层的局部变量,以及在表里记录当前所在的函数信息(return 要用);然后处理形参表,将形参加入局部变量;接着生成函数内的 statements 的具体代码;最后检查代码的最后一条指令是否是 return
      这里需要新建一个 %EOCF 符号,指向最后的 return,即如果该函数最后一条语句是 If 等控制流,那么这个 If 语句就知道它要跳转到最后的 return 了。(如果代码自己生成了 return,那么这个符号也是用不上的。)

    bool node_MethodDecl::generate_3addr_code() {
        bool err=0;
        symbolTab.push_back(SymbolTabType());                            // local label
        symbolTab.back()["%EOCF"]=Symbol(SB_LABEL,"EOCF_"+to_string(EOCFCnt++));    // 'End Of Control Flow' label
        // mark which function we are in
        symbolTab.back()["%FUNC"]=Symbol(SB_LABEL,"",((node_Type*)son[0])->varType,(node*)this);
        err|=((node_FormalParams*)son[2])->add_formal_params();        // formal parameters
        if (son.size()>=4) {
            err|=son[3]->generate_3addr_code();                        // statements
            add_codes(codes,son[3]->codes);
        }
        // check if the last instruction is return
        if (codes.empty() || codes.back().second!="return") {
            semantic_warning("lack of return at the end of function");
            codes.push_back(make_pair("","return"));
        }
        codes.back().first.insert(0,symbolTab.back()["%EOCF"].label+": ");
        codes[0].first.insert(0,"__"+((node_Id*)son[1])->name+": ");    // mark the entrance of function
        symbolTab.pop_back();
        return err;
    }
    

    Statements

      该节点表示语句的集合,同时也表示被 BEGIN, END 包起来的一个区块。所以逻辑就是先新建一层符号表表示局部变量,然后每条语句依次生成。
      但是遇到控制流语句的时候,要判断该语句是否是最后一句,如果是,那么 %EOCF 沿用祖先的(即控制流结束后跳转到祖先指定的地方),否则新建一个 %EOCF 指向下一条语句,并在下一条语句的第一个指令加上这个 label。

      听起来很简单

      但实际上。。。细思极恐,所谓“%EOCF指向下一条语句,并在下一条语句的第一个指令加上这个 label”,它可能需要处理这样的代码:

    IF (z==1) BEGIN
    	IF (i==1) BEGIN
    		IF (y==2) BEGIN
    		END
    		BEGIN
    		END
    		BEGIN
    			BEGIN
    			END
    		END
    	END
    	BEGIN
    		BEGIN
    		END
    	END
    	BEGIN
    	END
    END
    BEGIN
    	INT a;
    END
    

      这里的三个 if 全部都有“下一条语句”,但由于“下一条语句”为空,因此它们最终全都要使用祖先的 %EOCF,而不能新建 label 然后加到“下一条语句”。
      有同学说,既然这是空语句产生的 bug,那在生成语法树时就把这些空语句规避掉,不建立节点,不就好了?其一,空语句结构可能很复杂(比如这样嵌套的),从语法上处理它是比较麻烦的;其二,空语句不一定是形式上的空语句,还可以是实质空语句(就是写了非平凡的代码但生成的是空语句,例如因代码优化导致的空语句,例如局部变量定义也是不产生代码的),这导致一个不可规避的问题。

      解决方法是,遇到控制流,就把这一段连续的控制流语句抠出来,然后倒着做,相当于先要找到真正的非空的“下一条语句”在哪,然后倒序依次在这条语句的开头新建 %EOCF(或是决定用祖先的)给上一条语句用。

    bool isControlFlow(node *x) {
    	return (!x->son.empty() && (x->son[0]->nodeType==IFSTMT || x->son[0]->nodeType==FORSTMT));
    }
    
    bool node_Statements::generate_3addr_code() {
    	bool err=0;
    	symbolTab.push_back(SymbolTabType());		// local label
    	for(int i=0; i<son.size(); i++) {
    		node_Statement *x=(node_Statement*)son[i];
    		if (isControlFlow(x)) {
    			// for continuous control flows, generate codes in reverse order,
    			// to avoid bugs of EOCF label with empty statement
    			vector<node*> CFlist;
    			int j=i;							// i: the first CF;   j: after the last CF
    			for(; ; j++) {
    				for(; j<son.size() && isControlFlow(son[j]); j++) CFlist.push_back(son[j]);
    				if (j>=son.size()) break;
    				err|=son[j]->generate_3addr_code();
    				if (!son[j]->codes.empty()) break;
    			}
    			if (j<son.size()) {
    				string eocf="EOCF_"+to_string(EOCFCnt++);
    				symbolTab.back()["%EOCF"]=Symbol(SB_LABEL,eocf);
    				son[j]->codes[0].first.insert(0,eocf+": ");
    			}
    			for(int k=CFlist.size()-1; k>=0; k--) {
    				err|=CFlist[k]->generate_3addr_code();
    				if (k>i) {
    					string eocf="EOCF_"+to_string(EOCFCnt++);
    					symbolTab.back()["%EOCF"]=Symbol(SB_LABEL,eocf);
    					CFlist[k]->codes[0].first.insert(0,eocf+": ");
    				}
    			}
    			symbolTab.back().erase("%EOCF");
    			for(; i<=j; i++) if (i<son.size()) add_codes(codes,son[i]->codes);
    			i--;
    		} else {
    			err|=x->generate_3addr_code();		// each statement
    			add_codes(codes,x->codes);
    		}
    		
    	}
    	symbolTab.pop_back();
    	return err;
    }
    

    简单语句

      LocalVarDecl, AssignStmt, ReturnStmt, ReadStmt, WriteStmt 都是单条简单语句,它们生成的代码都较为简单,代码不赘述。
      ReturnStmt 需要注意若返回表达式的类型与函数类型不匹配,则要强制类型转换。
      ReadStmt, WriteStmt 当作函数调用处理。

      以 AssignStmt 为例:

    bool node_AssignStmt::generate_3addr_code() {
    	bool err=0;
    	string &leftName=((node_Id*)son[0])->name;
    	Symbol left=find_symbol(leftName);
    	if (left.type==SB_NOTFOUND) semantic_error("undeclared identifier '"+leftName+"'")
    		else if (left.type!=SB_VAR) semantic_error("identifier '"+leftName+"' is not a variable");
    	err|=son[1]->generate_3addr_code();
    	add_codes(codes,son[1]->codes);
    	codes.push_back(make_pair("",left.label+" = "+((node_Expression*)son[1])->resultLabel));
    	return err;
    }
    

    IfStmt

      If 是一个控制流,有 else 和没 else 的生成规则分别为:

    <codes of expression of condition>
    ifFalse <condition> goto %EOCF
    <codes of ifTrue>
    %EOCF: ...
    

      和

    <codes of expression of condition>
    ifFalse <condition> goto else
    <codes of ifTrue>
    goto %EOCF
    else: <codes of else>
    %EOCF: ...
    

      注意 ifTrue 和 else 的语句都要新建一层符号表。这里的 else label 要用 elseCnt 计数器来生成唯一标号。具体代码翻译该逻辑即可,不赘述。

    ForStmt

      For 语句共 4 个部分:初始化语句 init、循环条件 condition、每次循环结束后执行的操作 afterLoop、循环体 loop。生成规则为:

    <codes of init>
    <codes of condition>
    loop: ifFalse <condition> goto %EOCF
    <codes of loop>
    <codes of afterLoop>
    goto loop
    %EOCF: ...
    

    Expression

      每个 Expression 类要得到它的代码、返回类型、存放结果的 label。

    • 若为二元算术运算,则先生成左右两个子 Expression 的代码,然后两个返回值取类型优先级较高的作为最终结果类型,接着对两边进行强制类型转换,最后进行左右两个返回值的运算;
    • 若为二元逻辑运算,则同上,但最后结果类型是 BOOL
    • 若为一元运算,则先生成子 Expression 的代码,然后进行运算;
    • 若为立即数,则结果直接赋为该立即数;
    • 若为变量,则从符号表中寻找该变量的 label 作为结果 label;
    • 若为函数调用,则先生成实参的 Expression 的代码(并检查是否与形参匹配),接着用 param 语句传递参数,然后调用函数,最后取出返回值放到一个寄存器。

      这里是把布尔表达式和算数表达式等同对待了,但实际编译器是区别对待的,比如 or 运算,前件成立了是不判断后件的。本来这只是个代码优化,但它已经形成一种编程规范了,如果前件成立仍然判断后件会导致很多程序出错的。

      至此,三地址码的生成就基本说完了。

    后续内容

      做一做前端交互,用 -o 选项把代码输出到指定文件啥的,改一下上次的代码不要因为存在语法错误就把整棵树析构了,使其能够同时检查语法错误和语义错误,最好再按行号排个序输出。
      我现在的语言定义里还有很多东西没做,例如数组、指针、break、continue 之类的,因此这个语言还不是很完备。
      再后续,就是代码优化了,平台无关代码优化的部分龙书讲了很多,都挺有趣的。
      但是学期结束了哈哈哈哈哈哈哈哈哈哈

    展开全文
  • 在 ARM 汇编语言程序里,有一些特殊指令助记符,这些助记符与指令系统的助记符不同,没有相对应的操作,通常称这些特殊指令助记符为伪指令,他们所完成的操作称为伪操作。伪指令在源程序中的作用是为完成汇编程序...
  • 直接计算的语法制导翻译 (1)E→E1 or E2 { E.place := newtemp; emit(E.place ':=' E1.place 'or' E2.place);} (2) |E1 and E2 { E.place := newtemp; emit(E.place ':=' E1.place 'and' E2.place);} (3) |...
  • 计算机指令

    2021-05-25 02:09:00
    针对汇编代码,我们可以再用汇编器(Assembler)翻译成机器(Machine Code)。这些机器由“0”和“1”组成的机器语言表示。这一条条机器,就是一条条的计算机指令。这样一串串的16进制数字,就是我们CPU能够...
  • 在上篇文章我们聊到,无论什么语言写的代码,其到最后都是通过机器运行的,无一例外。那么对于 Java 语言来说,其从源代码到机器,这中间到底发生了什么呢?这就是今天我们要聊的。如下图所示,编译器可以分为:...
  • C语言if语句后面的表达式 C语言中if关键字之后(即括号内)均为表达式. 该表达式通常是逻辑表达式或关系表达式,但也可以是其它表达式,如赋值表达式等,甚至也可以是一个变量,这些变量的值都换算了逻辑值 0或非0.如...
  • 前言第7章 语法制导的语义计算语义分析是上下文有关的,目前较为常见的是用属性文法来描述程序语言语义,并采用语法制导翻译的方法完成对语法成分的翻译工作。属性文法属性 描述文法符号的类型、值等有关的一些信息...
  • 对于使用语句:select * from table1 where user_name <> ''来查询列user_name不为空(不为null且不为空字符)时,Oracle会查不出任何结果,而MySQL可以正常运行。这里MySQL之所以可以得到正确结果,还因为比较符号...
  • rpm、yum常用语句整理

    2021-06-07 20:08:02
    rpm、yum常用语句整理 | rpm软件安装乱炖
  • SQL语句学习总结01

    2021-11-01 11:03:05
    SELECT dname FROM dept WHERE dept IN(1,2,3)#查询dept中含123的编号 in只能查询统一字段中的条件 UUID UUID 是指Universally Unique Identifier,翻译为中文是通用唯一识别,UUID 的目的是让分布式系统中的所有...
  • 什么叫Cookie Cookie翻译成中文是小甜点,小饼干的意思.在HTTP中它表示服务器送给客户端浏览器的小甜点.其实Cookie是key-value结构,类似于一个python中的字典.随着服 ... 第十章 ReentrantLock 简介 Java 5.0 提供...
  • 计算机组成原理第四章答案.doc第4章习题参考答案1ASCII是7位,如果设计主存单元字长为32位,指令字长为12位,是否合理为什么答不合理。指令最好半字长或单字长,设16位比较合适。一个字符的ASCII是7位,如果设计...
  • Java 7 中的新功能[翻译]Java7中有一些令开发者很高兴的新特性,如switch语句中的使用字符串作为表达式,多catch异常处理,try-with-resources(自动资源管理),新的文件系统API,jvm扩展,支持动态类型语言,支持...
  • 25~21(5bits)Register Source(Rs) 表示源寄存器地址 16~20(5bits)Register Target(Rt) 表示选定目标寄存器地址 0~15(16bits)Immediate Number 表示立即数 Example : M Type(Memory) 机器组成类似于 ...
  • 视频地址 微信公众号【Python猫】, 本号更新连载高品质的系列产品文章内容,有Python为何系列产品、喵星社会学猫系列产品、Python升阶系列产品、推荐好书系列产品、技术性创作、高品质英语强烈推荐与汉语翻译这些,...
  • 本文主要记录如何在MySQL数据库中,一个字符串分割多条数据显示。外键有时是以字符串的形式存储,例如 12,13,14 这种,如果以这种形式存储,则不能直接与其他表关联查询,此时就需要该字段的值分割再关联查询...
  • 一,连接MySQL二,MySQL管理与授权,数据库简单操作四, 数据库备份五,后记一,连接MySQL格式:mysql -h 远程主机地址 -u 用户名 -p 回车输入密码进入:mysql -u root -p 回车Enter password: ,输入密码就可以进入...
  • 第2节-分支和循环语句

    千次阅读 2021-10-24 17:36:05
    1.什么是语句 C语句可分为以下五类: 1. 表达式语句 ( c=a+b; ) 2. 函数调用语句 ( printf("hehe\n"); ) 3. 控制语句 (本篇介绍) 4. 复合语句 5. 空语句 (一个;就是一个空语句) 本篇介绍的是控制语句...
  • 其中的一个极端就是直接的解释技术,另一个极端就是二进制翻译。解释包括取一条源指令,对其进行分析,执行需要的操作,再取下一条源指令这样一个循环过程,所有工作都是由软件完成的。另一方面,二进制解释试图分摊...
  • •JSP声明语句 •JSP表达式 1.JSPScriptlets JSPScriptlets是一段代码段。当需要使用Java实现一些复杂操作或控制时,可以使用它。JSPScriptlets的语法格式如下所示。 <%java代码(变量、方法、表达式等)%&...
  • Json解析的方法有很多种,而GsonFormat提供了非常简单的解决方案,用于使用Gson库JSONObject格式的String 解析实体,该插件可以加快开发进度,使用非常方便,效率高。但目前本插件只支持Android studio和 ...
  • 如果SS=6000H,则说明堆栈段物理地址起始于 〔 〕A.60H B.600H C.6000H D.60000H2.MASM语句中,表达常数不正确的形式是 〔 〕A.01101001B B.A346H C.’A’ D.56003.下列各个8位二进制数的补码中,真值最小的是〔 〕A....
  • 当C语言用来描述生活中的事物时,会用到种结构:顺序结构(不去赘述),选择结构(对应分支语句),循环结构(对应循环语句)。 分支语句:分支语句分为两种,一种是if语句,一种是switch语句。 if语句:if...
  • 对于ARM架构的CPU,上电后PC寄存器是指向0地址处的,从这个地址开始运行程序,那么运行了启动代码后会把程序搬移到内存中去运行,这样就是产生程序会在运行时有个两地址,而由源码编译为可执行文件时只会指定一个...
  • 用户代码段每一行代码的地址,及其对应的机器都显示给用户,为方便查看,还给出了反汇编得到的汇编指令,而且在注释中显示了用户编写的源代码。通过地址部分信息,我们知道每一行代码的地址都是前一行代码地址+4...
  • 外文翻译-通过php访问mysql.doc 1 译文 通过PHP 访问MySQL 现在你已经可以熟练地使用MySQL客户端软件来操作数据库里的数据,我们也 可以开始学习如何使用PHP来显示和修改数据库里的数据了。PHP有标准的函 数用来操作...
  • 注意力机制——transformer模型代码解析1:transformer图解分析(论文)1.1:论文中的模型图1.2:分模块解释1.2.1:单词、位置编码模块1.2.2:Encoder模块1.2.3:Decoder模块1.2.4:输出全连接层2:transformer代码...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 41,422
精华内容 16,568
关键字:

将语句翻译成三地址码