编译原理中控制的反向转移是怎么回事?

文章导读
Previous Quiz Next In code generation,循环和跳转指令在控制程序流程方面发挥着关键作用。Intermediate code generation 是编译器中的一个关键阶段,将高级语言结构转换为机器可解释的代码。虽然指令通常遵循线性序
📋 目录
  1. 什么是反向控制转移?
  2. 控制反向转移的类型
  3. Conclusion
A A

编译器中的反向控制转移



Previous
Quiz
Next

In code generation,循环和跳转指令在控制程序流程方面发挥着关键作用。Intermediate code generation 是编译器中的一个关键阶段,将高级语言结构转换为机器可解释的代码。虽然指令通常遵循线性序列,但在许多情况下,执行需要循环回到之前的指令。这种backward flow,称为reverse transfer of control,对于实现loops、recursionerror recovery至关重要。

在本章中,我们将讨论reverse transfer of control、其类型,以及编译器如何生成机器代码来处理backward jumps

什么是反向控制转移?

反向控制转移发生在执行跳转回之前的一组指令以重新执行时。那么,何时需要它?

反向控制转移在以下场景中很有用 −

  • Loops − 重复代码直到满足条件。
  • Function Recursion − 函数调用自身直到达到基本情况。
  • Error Recovery − 在失败后重试操作。

与前向转移不同,反向跳转专注于迭代和重复。现在编译器生成优化的指令(例如,J、BGE、JAL)来提供高效的反向控制流。

控制反向转移的类型

让我们检查一下控制反向转移的不同类型 −

循环 (While、For、Do-While)

循环依赖于向后跳转来重复执行代码块。编译器会策略性地放置条件检查和跳转指令。

示例:C 语言中的 While 循环

看一下以下示例 −

while (i < 10) {  
   i = i + 1;  
}  

使用 Atoms 的中间表示 (IR)

(LBL, L1)  
(TST, i, 10, <, L2)   # 如果 i >= 10,退出循环  
(ADD, i, 1, i)        # i = i + 1  
(JMP, L1)             # 重复循环  
(LBL, L2)

翻译为机器码 (MIPS 示例)

L1:  
    lw $t1, i  
    li $t2, 10  
    bge $t1, $t2, L2   # 如果 i >= 10,退出循环  

    addi $t1, $t1, 1   # i = i + 1  
    sw $t1, i  
    j L1               # 重复循环  

L2:  

在这里,我们可以看到 j L1 指令将控制转移回后方,以持续执行循环。

Do-While 循环

让我们看看另一种类型的循环。这是一种出口控制循环。与 while 循环不同,do-while 循环在检查条件之前总是至少执行一次。编译器将循环条件检查放置在末尾,如果条件仍然有效则向后跳转。

示例:C 语言中的 Do-While 循环

看一下以下示例 −

do {  
   i = i + 1;  
} while (i < 10);   

使用 Atoms 的中间表示 (IR) −

(LBL, L1)  
(ADD, i, 1, i)        # i = i + 1  
(TST, i, 10, <, L1)   # 如果 i < 10,重复循环  

翻译为机器码 (MIPS 示例) −

L1:  
    addi $t1, $t1, 1   # i = i + 1  

    lw $t1, i  
    li $t2, 10  
    blt $t1, $t2, L1   # 如果 i < 10,重复循环  

blt(小于则分支)指令将控制转移回 L1,重复执行。

For 循环

for 循环也是一种入口控制循环。但它以结构化形式包含初始化、条件检查和递增。编译器生成跳转来处理条件检查和迭代步骤。

示例:C 语言中的 For 循环

看一下以下示例 −

for (i = 0; i < 10; i++) {  
   sum = sum + i;  
}   

使用 Atoms 的中间表示 (IR) −

(MOV, 0, , i)         # i = 0  
(LBL, L1)  
(TST, i, 10, <, L2)   # 如果 i >= 10,退出循环  
(ADD, sum, i, sum)    # sum = sum + i  
(ADD, i, 1, i)        # i = i + 1  
(JMP, L1)             # 重复循环  
(LBL, L2)   

翻译为机器码 (MIPS 示例) −

    li $t1, 0          # i = 0  
L1:  
    bge $t1, 10, L2    # 如果 i >= 10,退出循环  

    add $t3, $t3, $t1  # sum = sum + i  
    addi $t1, $t1, 1   # i = i + 1  
    j L1               # 重复循环  

L2:  

在这里,我们可以看到条件检查和递增操作是分开的,j L1 将控制转移回后方。

函数递归

对于基于函数的方法如递归,会发生向后转移。在递归函数调用中,执行跳转回函数的起点,直到满足基本条件。

示例:C 语言中的阶乘函数

看一下以下示例 −

int factorial(int n) {  
   if (n == 0) return 1;  
   return n * factorial(n - 1);  
}   

使用 Atoms 的中间表示 (IR) −

(TST, n, 0, ==, L1)   # 如果 n == 0,返回 1  
(SUB, n, 1, T1)  
(CALL, factorial, T1, T2)  
(MUL, n, T2, R)       # R = n * factorial(n-1)  
(LBL, L1)  
(MOV, 1, , R)

翻译为机器码 (MIPS 示例) −

factorial:  
    beq $a0, 0, base_case   # 如果 n == 0,返回 1  

    addi $sp, $sp, -4  
    sw $ra, 0($sp)          # 保存返回地址  

    addi $a0, $a0, -1  
    jal factorial           # 递归调用  

    lw $ra, 0($sp)  
    addi $sp, $sp, 4  
    mul $v0, $a0, $v0       # n * factorial(n-1) 的乘法  
    jr $ra                  # 返回  

base_case:  
    li $v0, 1               # 返回 1  
    jr $ra 

每次递归调用都将控制转移回 factorial,从而执行另一个函数调用,直到达到基本情况。

为了轻松记住控制反向转移,您可以参考下表 −

类型 用法 机器指令
While 循环 带条件检查的迭代 BGE, BLT, J
Do-While 循环 确保至少执行一次 BLT, J
For 循环 结构化迭代 BGE, ADD, J
递归 函数调用返回 JAL, JR, BEQ

Conclusion

在本章中,我们探讨了编译器设计中的reverse transfer of control(反向控制转移)概念,这是处理loops(循环)和recursion(递归)的根本。在编译器将高级结构如while循环、for循环和递归函数翻译成分支和跳转指令,以实现高效的反向执行。

我们介绍了编译器如何管理backward jumps(反向跳转),开发者如何编写优化的代码,以及迭代和递归在低级层面如何工作。无论是简单的循环还是复杂的递归算法,反向控制转移都能确保程序重复且可预测地执行。