《编译原理-龙书》阅读笔记 第一章

1.1 语言处理器

  1. 编译器和解释器的区别

编译器是把源程序的每一条语句都编译成机器语言,并保存成二进制文件,这样运行时计算机可以直接以机器语言来运行此程序,速度很快;而解释器则是只在执行程序时,才一条一条的解释成机器语言给计算机来执行,所以运行速度是不如编译后的程序运行的快的。

  1. Java语言处理器结合了编译和解释过程

一个Java程序先被编译成字节码(bytecode)的中间表示形式,然后由虚拟机对字节码加以解释执行

  1. 语言处理器的组成

1个语言处理器包含:预处理器->编译器->汇编器->链接器/加载器

1.2 一个编译器的结构

  1. 编译器把源程序映射为等价的目标程序,这个映射过程由两个部分组成:分析部分和综合部分
    1. 分析(analysis)部分把源程序分解成为多个组成要素,并在这些要素之上加上语法结构。然后,它使用这个结构来创建该源程序的一个中间表示。如果分析部分检查出源程序没有按照正确的语法构成,或者语义上不一致,它就必须提供有用的信息,使得用户可以按此进行改正。分析部分还会收集有关源程序的信息,并把信息存放在一个称为符号表(symbol table)的数据结构中。符号表将和中间表示形式一起传送给综合部分;
    2. 综合(synthesis)部分根据中间表示和符号表中的信息来构造用户期待的目标程序。

分析部分经常被称为编译器的前端(front end),而综合部分称为后端(back end)

  1. 编译器包含的各个步骤:词法分析->语法分析->语义分析->中间代码生成器->机器无关代码优化器->代码生成器->机器代码相关优化器

1.2.1 词法分析

  1. 词法分析器读入组成源程序的字符流,并且将它们组织成为有意义的词素的序列
  2. 一个赋值语句的翻译

1.2.2 语法分析

  1. 语法分析器

语法分析器使用由词法分析器生成的各个词法单元的第一个分量来创建树形的中间表示。该中间表示给出了词法分析产生的词法单元流的语法结构。一个常用的表示方法是语法树(syntax tree),树中的每个内部结点表示一个运算,而该结点的子结点表示该运算的分量。

1.2.3 语义分析

  1. 语义分析器

语义分析器(semantic analyzer)使用语法树和符号表中的信息来检查源程序是否和语言定义的语义一致。它同时也收集类型信息,并把这些信息存放在语法树或符号表中,以便在随后的中间代码生成过程中使用。

  1. 类型检查

语义分析的一个重要部分是类型检查(type checking)。编译器检查每个运算符是否具有匹配的运算分量

  1. 自动类型转换

程序设计语言可能允许某些类型转换,这被称为自动类型转换(coercion)。比如,一个二元算术运算符可以应用于一对整数或者一对浮点数。如果这个运算符应用于一个浮点数和一个整数,那么编译器可以把该整数转换(或者说自动类型转换)成为一个浮点数。

1.2.4 中间代码生成

  1. 概念解释

在源程序的语法分析和语义分析完成之后,很多编译器生成一个明确的低级的或类机器语言的中间表示。我们可以把这个表示看作是某个抽象机器的程序。该中间表示应该具有两个重要的性质:它应该易于生成,且能够被轻松地翻译为目标机器上的语言。

  1. 三地址代码

三地址代码(three-address code)是一种中间表示形式。这种中间表示由一组类似于汇编语言的指令组成,每个指令具有三个运算分量。每个运算分量都像一个寄存器。

  1. 三地址代码的3个要点
    1. 首先,每个三地址赋值指令的右部最多只有一个运算符。因此这些指令确定了运算完成的顺序。比如乘法应该在加法之前完成。
    2. <
### 编译原理龙书第8章练习题解答 #### 构建语法指导翻译方案 构建一个用于将后缀算术表达式转换为前缀算术表达式的语法指导翻译方案[^1]。此过程涉及定义一系列语法规则,这些规则不仅描述了如何解析输入字符串,还指定了每条规则触发时应执行的动作来生成目标形式的输出。 对于这个问题而言,可以设计一套上下文无关文法(CFG),其中每个产生式都关联着特定的操作以实现从逆波兰表示到标准数学记号的变化。例如: - 当遇到操作数时立即将其压入栈中; - 若读取到了运算符,则弹出所需数量的操作数并构造新的前缀表达式节点,之后再把该新创建的结果重新推回堆栈顶端等待后续处理; 最终当全部输入被消耗完毕以后,留在栈底的就是完整的前缀版本计算公式。 ```python def translate_postfix_to_prefix(expression): stack = [] for token in expression.split(): if is_operand(token): # 假设有一个函数is_operand判断是否为操作数 stack.append(token) elif is_operator(token): # 同样假设存在这样的辅助方法 operand2 = stack.pop() operand1 = stack.pop() new_expression = f"{token} {operand1} {operand2}" stack.append(new_expression) return stack[-1] ``` 这段Python代码片段展示了如何通过遍历给定的后缀表达式列表,并利用栈结构来进行相应的变换工作。 #### DFA最小化算法应用实例 考虑给出的状态机简化案例,在这里原始自动机拥有多个非终止态以及若干个接受态组成的集合。为了减少冗余路径数目从而优化模式匹配效率,采用了一种基于等价类划分的方法对初始配置进行了压缩处理[^2]。具体来说就是先区分可区别的终态和非终态两大部分,接着不断细分直到不能再找到任何具有相同行为特征但归属于不同组别的情况为止。这样做的好处是可以显著降低复杂度而不改变识别能力。 #### 符号集的概念及其作用范围说明 在讨论编译器前端理论时经常会接触到`FIRST`、`FOLLOW`还有`NULLABLE`这三个重要概念[^3]。它们主要用于帮助预测分析过程中可能出现的各种情况,以便于更精确地控制句型展开的方向性和合法性验证机制的设计思路。比如在一个含有间接左递归现象的文法里头,就需要借助`FIRST/FOLLOW`集合的信息去消除潜在冲突点,确保LL(1)解析能够顺利进行下去而不会陷入死循环或者错误决策之中。 #### 控制流图与基本块划分示例 针对一段简单的程序逻辑,可以通过绘制对应的控制流程图形直观展示各个分支之间的关系走向[^4]。在这个例子里面可以看到整个过程被自然分割成了十一个小节——即所谓的“基础区块”,每一个都是线性的指令序列并且只有一处入口一处出口特性。这种抽象层次上的拆解有助于深入理解内部运作细节的同时也为进一步开展诸如寄存器分配之类的底层优化提供了便利条件。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值