mips汇编:理解递归

时间:2013-05-12 18:54:11

标签: assembly recursion mips

所以我有这个代码 计算阶乘(一般) 但在这个例子中,它计算的阶乘为10

    .data 0x10008000
    .word 10
    .word 1
    .word 0
    .ascii "The factorial of 10 is %d \n"
    .text
    .globl main
main:
    addi $sp, $sp, -32
    sw $ra, 20($sp)
    sw $fp, 16($sp)
    addiu $fp, $sp,28
    lw $a0, 0($gp)
    jal fact

    ...

    lw $ra, 20($sp)
    lw $fp, 16($sp)
    addiu $sp, $sp, 32
    jr $ra
fact:
    addiu $sp, $sp, -32
    sw $ra, 20($sp)
    sw $fp, 16($sp)
    addiu $fp, $sp, 28
    sw $a0, 0($fp)
    lw $v0, 0($fp)      
    lw $t0, 4($gp)
    slt $t1,$v0,$t0     
    bne $t0, $t1, L2    
    addi $v0, $v0, 1
    jr L1
 L2:
    lw $v1, 0($fp)
    addi $v0, $v1, -1
    sw $v0, 8($gp)
    lw $a0, 8($gp)
    jal fact                   
    lw $v1, 0($fp)      
    mul $v0, $v0, $v1    
 L1:
    lw $ra, 20($sp)
    lw $fp, 16($sp)
    addiu $sp, $sp, 32
    jr $ra

我的问题是这个 不需要一个 jr L1 L2中乘法后的命令? 递归是如何工作的? 是否需要某种方式来存储以前的数字? 我认为这是堆栈的工作,但在我看来每次都是这样 该函数被称为先前存储的变量ars覆盖。

ps我不知道是否有人理解我的问题 对不起我的英语很差......

2 个答案:

答案 0 :(得分:2)

fact函数的工作方式如下:

int fact(unsigned int n) {
  if (n < 1) {
    return n+1;
  } else {
    return fact(n-1) * n;
  }
}

正如你所看到的,它递归调用自己直到参数变为< 1,此时它返回1(即0+1)并返回调用链以执行乘法运算

例如,如果您执行fact(3);,则调用链将如下所示:

fact(3): fact(2) * 3
  fact(2): fact(1) * 2
    fact(1): fact(0) * 1
      fact(0): 1
      1
    * 1 (==1)
  * 2 (==2)
* 3 (==6)

函数的值在$v0中返回。要获取n的当前值以执行乘法fact(n-1) * n,函数会将其存储在当前堆栈帧(sw $a0, 0($fp))上并在乘法之前将其读回({{1} })。
正如Michael Burr评论的那样,在lw $v1, 0($fp)的每个条目上都会创建一个新的堆栈帧。这就是fact的前4个指令所做的事情(在堆栈上保留一些空间,保存当前帧指针和返回地址,并将帧指针指向新的堆栈帧)。

在乘法之后没有必要跳转到fact,因为L1紧随其后(即无论如何都会到达标签)。

答案 1 :(得分:0)

递归需要引入“手动堆栈管理”,这显然需要付出一些努力才能实现。 并非所有编译器都出于各种原因支持它

这一切都发生在堆栈上,它在不同的参数树之间扩展和收缩,是一件非常令人惊奇的事情。

如果你想“了解那些东西”,我建议你对“河内塔”做一些研究,这是一个高度递归的现实问题

这里有一个很好的解释

http://www.cs.cmu.edu/~cburch/survey/recurse/hanoiimpl.html

一旦理解了递归,它就可以用于非常复杂的问题,比如数独求解器

典型的数独游戏有大约20到30种不同的场景

因此,可以通过生成单个递归解决方案来规避20多个手动场景的编程,这个解决方案也可以为“不可能的”sudokos神奇地工作