【问题标题】:Double recursion using MIPS使用 MIPS 的双重递归
【发布时间】:2016-02-24 04:19:06
【问题描述】:

我正在尝试为函数f(n) = 2f(n-1) + 3f(n-2) + 1 实现双重递归。我能够找出奇异递归并实现它的2f(n-1) + 1 部分,但我不知道如何实现第二部分。这是我的奇异递归的工作代码:

.data
prompt1: .asciiz "Enter the value for the recursive function f(n) = 2f(n-1)+3f(n-2)+1:  "
prompt2: .asciiz "Result: "
numberEntered: .word 0
answer: .word 0

.text

main:
#Ask for the value
li $v0 4
la $a0, prompt1
syscall

#enter value
li $v0, 5
syscall

sw $v0, numberEntered  #stores the number

# call the function
lw $a0, numberEntered
jal function
sw $v0, answer

#Print out the result
li $v0 4
la $a0, prompt2
syscall

lw $a0, answer
li $v0, 1
syscall

li $v0, 10
syscall

.globl function
function:
subu $sp, $sp, 8
sw $ra, ($sp)
sw $s0, 4($sp)

#base case
li $v0, 1
beq $a0, $zero, done

#calling f(n-1)
move $s0, $a0
sub $a0, $a0, 1
jal function

#calculations occur here
mul $v0, $v0, 2
addi $v0, $v0, 1

done:

lw $ra, ($sp)
lw $s0, 4($sp)
addi $sp, $sp, 8

jr $ra  #returns

我尝试在它的计算部分将下一部分的地址加载到堆栈中,但我无法弄清楚如何让它工作。我必须两次“结束”堆栈吗?曾经是目前的情况,一次是在计算部分?我无法弄清楚,任何帮助将不胜感激!

【问题讨论】:

    标签: recursion mips


    【解决方案1】:

    相当努力。

    回答您的问题:您可以简单地在function 条目处建立一个堆栈框架,并在最后从中恢复。

    我确实不得不稍微改变 $s0 的用途来存储一个中间值并将其添加到堆栈帧中的存储值中(即堆栈帧现在是 3 个字而不是 2 个字)。

    无论如何,这是更正后的代码。我尝试对其进行注释(IMO,在 asm 中,没有“太多 cmets”之类的东西)[请原谅无偿的风格清理]:

        .data
    prompt1:    .asciiz     "Enter the value for the recursive function f(n) = 2f(n-1)+3f(n-2)+1:  "
    prompt2:    .asciiz     "Result: "
    numberEntered:  .word   0
    answer:     .word       0
    
        .text
    
    main:
        # Ask for the value
        li      $v0,4
        la      $a0,prompt1
        syscall
    
        # enter value
        li      $v0,5
        syscall
    
        sw      $v0,numberEntered       # stores the number
    
        # call the function
        lw      $a0,numberEntered
        jal     function
        sw      $v0,answer
    
        # Print out the result
        li      $v0,4
        la      $a0,prompt2
        syscall
    
        lw      $a0,answer
        li      $v0,1
        syscall
    
        li      $v0,10
        syscall
    
        .globl  function
    
    # function -- calculation
    # v0 -- return value
    # a0 -- current n value
    # s0 -- intermediate result
    function:
        subi    $sp,$sp,12              # establish our stack frame
    
        sw      $ra,8($sp)              # save return address
        sw      $a0,4($sp)              # save n
        sw      $s0,0($sp)              # save intermediate result
    
        # base case
        # NOTE: with the addition of n-2, the "beq" was insufficient
        li      $v0,1
        ble     $a0,$zero,done
    
        # calling f(n-1)
        sub     $a0,$a0,1               # get n-1
        jal     function
    
        # NOTE: these could be combined into a single instruction
        mul     $v0,$v0,2               # get 2f(n-1)
        move    $s0,$v0                 # save it
    
        # calling f(n-2)
        sub     $a0,$a0,1               # get n-2
        jal     function
    
        mul     $v0,$v0,3               # get 3f(n-2)
    
        # get 2f(n-1) + 3f(n-2) + 1
        add     $v0,$s0,$v0
        add     $v0,$v0,1
    
    done:
        lw      $ra,8($sp)              # restore return address
        lw      $a0,4($sp)              # restore n
        lw      $s0,0($sp)              # restore intermediate result
    
        addi    $sp,$sp,12              # pop stack frame
    
        jr      $ra                     # returns
    

    更新:

    这个解决方案比我想象的要简单。

    这可能是因为堆栈帧在 asm [或 C] 中完成的方式。

    考虑一个现代 C 程序:

    int
    whatever(int n)
    {
        int x;
    
        // point A
        x = other(1);
    
        // do lots more stuff ...
    
        {
            // point B
            int y = other(2);
    
            // do lots more stuff ...
    
            // point C
            n *= (x + y);
        }
    
        // do lots more stuff ...
    
        n += ...;
    
        return n;
    }
    

    C 编译器将在进入whatever 时建立一个堆栈帧,为x 保留空间 y,即使y 只是设置很晚。大多数 C 程序员都没有意识到这一点。

    在解释型语言(例如javapython 等)中,y 的空间未被保留直到point B 处的代码被执行。而且,当y“超出范围”[在point C之后]时,他们[通常]会释放它。

    大多数 C 程序员认为具有范围声明 [如 y] 可以节省堆栈空间。 (即)在作用域块中,编译器将增加堆栈帧大小以容纳 y,并在不再需要 y 时再次缩小它。

    这完全是正确的。 C 编译器将为 所有 函数作用域变量设置堆栈框架并保留空间,即使它们是在函数后期或在内部作用域内声明的 [如 y]。

    这正是我们在您的function 中所做的。即使直到函数中间我们才需要/使用 $s0 的堆栈空间 [在偏移 0 处],这也是正确的。

    所以,我猜测您使用其他语言的经验确实 [有效地] 动态保留空间或关于 C 的普遍智慧可能使您找到了一个更复杂的模型来表达您的想法必需的。因此,您最初的问题是:我是否必须“收起”堆栈两次?


    我还应该提到function 的调用约定符合 ABI。如果它是自包含的(即不调用其他任何东西),这完全很好。也就是说,在 asm 中,对于“叶”函数,我们可以定义任何我们想要的。

    原因是$a0 调用被callee 修改/删除。我们从堆栈中保存/恢复它,但调用另一个函数可能会搞砸。补救方法就是使用另一个寄存器来保存值[或保存/恢复到堆栈帧中的额外位置],因此如果function 最终调用其他内容,则需要做更多工作。

    【讨论】:

    • 感谢您的帮助!这个解决方案比我想象的要简单得多。
    • 不客气!在考虑了你原来的问题之后 [re.第二个堆栈],我添加了有关堆栈框架模型的更多解释。我还添加了一些关于 ABI 的内容
    • 现在更有意义了,感谢您所做的一切!你超出了我的要求。
    • 我在您的代码中发现了一个小错误。在注释 #get 2f(n-1) + 3f(n-2) + 1 下的行上,应该添加 $v0, $s0, $v0 然后添加 $v0, $v0, 1。您使用了 $s0跟踪一个值,但在最后的计算中忽略它。我还介绍了第二个基本案例,让整个事情正常工作。有什么地方我应该发布代码吗?
    • 你说得对——我错过了。我将为后代编辑我的代码。您可以编辑问题并将其添加到那里。但是,一般来说,在这里,您可以完全为您自己的问题提供答案。大多数OP都不会打扰。但是,如果没有答案并且 OP 自己解决问题,通常会这样做。或者,如果有多个不完整的答案(例如 4 个答案,每个答案只能解决 25%),则在组合时,给 OP 答案[可能由 OP 提供一些附加代码]。对于细微的调整,通常是向响应者 [他修复] 的评论。
    猜你喜欢
    • 2014-03-19
    • 2011-05-20
    • 2016-07-13
    • 1970-01-01
    • 2021-09-07
    • 1970-01-01
    • 2016-01-27
    • 2013-10-13
    • 2014-03-28
    相关资源
    最近更新 更多