下一页 上一页 目录

2. 关于编译器的一些通用概念

编译器是一种翻译器,它接受用源语言编写的程序,并将其转换为另一种语言的程序,这种语言通常是真实(或虚拟)微处理器的汇编代码。设计真正的编译器是一项复杂的工作,需要计算机科学和数学方面的正规培训。

编译过程可以分为若干个称为阶段的子任务。涉及的不同阶段包括

  1. 词法分析
  2. 语法分析
  3. 中间代码生成
  4. 代码优化
  5. 代码生成。

符号表和错误处理程序参与上述所有阶段。让我们看看这些阶段。

  1. 词法分析

    词法分析器读取源程序并发出标记(tokens)。标记是原子单元,代表字符序列。它们可以被视为单个逻辑实体。标识符、关键字、常量、运算符、标点符号等都是标记的例子。考虑一下 C 语句:

                    return 5;
    

    它包含 3 个标记。关键字“return”、常量 5 和标点符号分号。

  2. 语法分析

    来自词法分析器的标记是此阶段的输入。将为任何编程语言提供一组规则(也称为产生式)。这定义了语言的语法。语法分析器检查给定的输入是否有效。即。它是否被给定的语法允许。我们可以编写一小组产生式。

            Sentence        -> Noun Verb
            Noun            -> boy  | girl | bird
            Verb            -> eats | runs | flies
    

    这是一个没有实际重要性(也没有意义)的语法。但它粗略地说明了语法的概念。

    语法的解析器以字符串作为输入。它可以生成解析树作为输出。有两种类型的解析是可能的。自顶向下解析和自底向上解析。含义从名称中可以清楚地看出。自底向上解析器从叶子开始并遍历到树的根。

    在上面的语法中,如果我们将“bird flies”作为输入,则可以追溯到有效“句子”的根。自底向上解析的一种类型是“移位-归约”解析。这里使用的一般方法是获取输入符号并将其压入堆栈,直到产生式的右侧出现在堆栈顶部。在这种情况下,它会用左侧进行归约。因此,它包括移位输入并在可能的情况下进行归约。可以使用 LR(从左到右)自底向上解析器。

  3. 中间代码生成。

    一旦确定了语法结构,编译器就可以为每个结构生成目标代码。但编译器会创建一种中间形式。它有助于代码优化,并且可以在机器无关阶段(词法、语法)和机器相关阶段(优化、代码生成)之间进行清晰的分离。

    中间代码的一种形式是解析树。解析树可能包含作为终端节点的变量。二元运算符将具有用于操作数 1 和操作数 2 的左分支和右分支。

    另一种中间代码形式是三地址代码。它具有 A = B op C 的通用结构,其中 A、B 和 C 可以是名称、常量、临时名称等。op 可以是任何运算符。后缀表示法是另一种中间代码形式。

  4. 优化

    优化涉及改进从源程序创建的目标代码的技术。可以从源程序生成大量目标代码。一些目标代码可能相对更好。优化是寻找更好的代码(可能不是最好的,但更好)。

    许多技术用于优化。算术简化、常量折叠是其中的一些。循环是此阶段的主要目标。这主要是因为程序在内循环中花费了大量时间。不变量从循环中移除。

  5. 代码生成

    代码生成阶段将生成的中间代码转换为一系列机器指令。如果我们使用简单的例程进行代码生成,可能会导致许多冗余的加载和存储。应避免这种低效的资源利用。一个好的代码生成器可以有效地使用其寄存器。

  6. 符号表

    源程序中会出现大量名称(例如变量名)。编译器需要收集有关这些名称的信息并正确使用它们。用于此目的的数据结构称为符号表。编译器的所有阶段都以一种或另一种方式使用符号表。

    符号表可以用多种方式实现。范围从简单的数组到复杂的哈希方法。我们必须将新名称和信息插入到符号表中,并在需要时恢复它们。

  7. 错误处理。

    一个好的编译器应该能够以最有效的方式检测错误并向用户报告错误。错误消息应该高度可理解和灵活。错误可能是由多种原因引起的,从简单的打字错误到编译器包含的复杂错误(应不惜一切代价避免)。


下一页 上一页 目录