设计问题

2025年6月24日 | 阅读 4 分钟

在本文中,我们将详细解释代码生成、其设计问题、局限性等概念。

您对代码生成是什么意思?

它用于将源代码的中间表示转换为机器可读格式。

代码生成器函数列表

  • 它用于生成编译器可以执行的正确且高效的代码。
  • 它易于实现、测试和维护。

在代码生成阶段,可能会出现各种问题

  1. 代码生成器的输入
  2. 目标程序
  3. 内存管理
  4. 指令选择
  5. 寄存器分配
  6. 求值顺序

1. 代码生成器的输入

  • 代码生成器的输入包含源代码的中间表示和符号表的信息。 源代码由前端生成。
  • 中间表示有几种选择
      a) 后缀表示法
      b) 语法树
      c) 三地址码
  • 我们假设前端生成低级中间表示,即其中的名称的值可以直接由机器指令操作。
  • 代码生成阶段需要完整的、无错误的中间代码作为输入。

2. 目标程序

目标程序是代码生成器的输出。 输出可以是

a) 汇编语言: 它允许子程序单独编译。

b) 可重定位机器语言: 它使代码生成过程更容易。

c) 绝对机器语言: 它可以放置在内存的固定位置并立即执行。

3. 内存管理

  • 在代码生成过程中,必须将符号表条目映射到实际地址,并将级别映射到指令地址。
  • 源代码中的名称到数据地址的映射由前端和代码生成器协同完成。
  • 局部变量在激活记录中进行堆栈分配,而全局变量在静态区域中。

4. 指令选择

  • 目标机器的指令集的性质应该是完整的和统一的。
  • 当你考虑目标机器的效率时,指令速度和机器习惯用法是重要的因素。
  • 生成的代码的质量可以通过其速度和大小来确定。

示例

三地址码是

低效的汇编代码是

说明

在上面的代码中,不需要第四条语句,因为在上一条语句中存储的值被重新加载到该语句中。 这会导致低效的代码序列。

高效的汇编代码是

说明

给定的中间表示可以用多种代码排列方式解释,不同的实现之间存在显着的成本差异。

5. 寄存器分配

寄存器的访问速度比内存快。 涉及寄存器中操作数的指令比涉及内存操作数的指令短且快。

当我们使用寄存器时,有以下 2 个步骤

寄存器分配: 在寄存器分配中,我们选择将驻留在寄存器中的变量集。

寄存器分配: 在寄存器分配中,我们选择包含变量的寄存器。

某些机器需要偶数-奇数寄存器对来执行某些操作数和结果。

寄存器的问题

很难控制将哪些变量分配给哪些寄存器,尤其是在可用寄存器数量有限的某些情况下。 不正确的寄存器分配会导致溢出,数据会在内存中短暂存储,从而降低性能。

为了理解这个概念,让我们来看下面的三地址码序列

它们的组织良好的机器代码序列如下所示

例如

考虑以下形式的除法指令

其中,

x 是偶数/奇数寄存器对中的被除数

y 是除数

偶数寄存器用于保存余数。

奇数寄存器用于保存商。

6. 求值顺序

目标代码的效率可能会受到执行计算的顺序的影响。 一些计算顺序需要比其他计算顺序更少的寄存器来保存中间结果。

代码生成器的局限性

  • 它无法灵活地容纳大量输入或为不同的目标平台生成代码。
  • 调试生成的代码可能不容易阅读或理解,因为它比手动代码更难。

代码生成的挑战

设计代码生成器时的主要挑战是确保生成的代码正确、高效且可靠。

  • 正确: 它创建的代码正确地返回了源代码的逻辑。 如果程序中存在错误,它可能会导致系统中的不正确行为。
  • 更新: 它必须易于维护和更新。
  • 测试: 生成的代码应该易于测试。 适当的测试有助于轻松获取问题并确保生成器生成可靠的输出。

关于编译器设计中设计问题的常见问题

1. 设计代码生成器时遇到的各种问题是什么?

答案: 在设计代码生成时,会出现各种问题,例如向代码生成器提供数据、目标程序、内存管理、指令选择、寄存器分配和求值顺序。


下一个主题目标机器