assembly mips汇编中的fibonacci序列

2lpgd968  于 2023-06-06  发布在  其他
关注(0)|答案(1)|浏览(178)

所以,我已经试了两天写一个程序在mips大会行使我的考试在几天内,但是的,我的大脑一直滞后,我不明白它应该如何工作,我应该做什么,非常混乱。不幸的是,我写的所有版本的程序都不能计算斐波那契数列。

So this is my code as of now
.data
prompt: .asciiz "Give me a number to find its fibonacci: "
result: .asciiz "The fibonacci is: "
number: .word   0
answer: .word   0
        .text
        .globl  main
        .globl  fib
main:   li      $v0, 4
        la      $a0, prompt
        syscall
        
        li      $v0, 5
        syscall
        
        sw      $v0, number
        lw      $a0, number
        
        li      $v0, 0
        jal     fib

        sw      $v0, answer
        
        li      $v0, 4
        la      $a0, result
        syscall
        
        li      $v0, 1
        lw      $a0, answer
        syscall
        
        li      $v0, 10
        syscall
        
fib:    addi    $sp, $sp, -8
        sw      $ra, 4($sp)
        sw      $a0, 0($sp)
        
        slti    $t0, $a0, 2         # if (t0=(a0 < 2))
        beqz    $t0, fibB           # if (t0 == 0) / a0 < 2 false goto fibB
        # if (t0 == 1)
        add     $v0, $a0, $v0
        addi    $sp, $sp, 8
        jr      $ra
        
fibB:   addi    $a0, $a0, -1
        jal     fib
        addi    $a0, $a0, -2
        jal     fib
        lw      $a0, 0($sp)
        lw      $ra, 4($sp)
        addi    $sp, $sp, 8
        jr      $ra
plicqrtu

plicqrtu1#

如果您选择使用自定义/非标准调用约定,既保存和恢复$a0,又将$v0作为累加器参数传递,那么您就远离了主流轨道。有可能你的导师告诉你这样做,但你需要去他们那里寻求帮助,或者至少获得这种改变的知识。
恕我直言,这种方法充其量可以被认为是一种非常先进的优化技术(尽管有缺陷或不完整),所以在你知道正常调用应该如何工作之前,学习这种方法几乎没有什么好处。
我还看到你试图将表达式fib(...) + fib(...)的加法部分移到基本情况中--这是愚蠢的,因为它根本不属于那里,然后从所有非基本情况中消失,结果函数根本不是正常的递归fib()
这段代码正在做类似这样的事情:

int altFib(int n, int acc) {
    if (n <= 2) {
        return n + acc;
    }
    acc = altFib(n-1, acc);
    return altFib(n-2, acc);
}

所以,这只会在基本情况下做加法,而不是在每个级别上。
另外,需要明确的是,在互联网上可以找到一些不好的例子,fib()使用了许多教育工作者认为有问题的非标准调用约定,但其他人甚至不理解这些问题。
这篇文章的其余部分将处理标准调用约定和寄存器用法。
首先,你的基本案例有一个错别字(或设计缺陷):add $v0, $a0, $v0。在基本情况下,您希望简单地将$a0复制到$v0中,而不是将$v0中的任何内容与$a0相加,即return n;
总是先用最小的数字测试你的代码,比如fib(0),然后fib)(1),然后fib(2)。但是,在调用函数之前,您已经巧妙地将$v0清除为0,因为非标准的创可贴-$v0不是函数的参数,而且您的创可贴没有应用于fib中的内部调用站点。我们不应该依赖于$v0在函数入口时有一些值:简单地设置它,覆盖任何旧值,以向调用者返回所需的值。
第二,在fib函数中,对于在函数中第二次调用fib,通常不遵循标准调用约定。

  • 您正在使用非标准的寄存器保存和恢复,$a0不应该被恢复,并且调用者不应该依赖于被调用者恢复它。 $a0应该为了你自己的函数的利益而保存,但是没有理由为了调用者的利益而恢复它。
  • fib的第一次调用的返回值(假设)在$v0中,但您对fib进行了第二次(递归)调用,这必然也会返回$v0中的返回值,因此您不再能够访问第一次调用的返回值;它丢失了!您可以在调试中观察到这一点。(非标准调用约定将恢复调用获得的$a0,而不是您想要的原始参数值,这意味着它将在$a0中返回-1值,但正确的解决方案是使用标准调用约定。
  • 参数$a0在第一次调用fib时被清除,您自己的代码将其减去1,以使该调用=。您可以在调试中观察到这一点,只需使用fib(2)
  • 您缺少fib(..) + fib(..)中的加法。您很可能错过了这一点,因为您不知道要添加什么(或者可能是由于替代设计),这是由于没有遵循调用约定的要求,以及第一次调用fib时第一个$v0返回值的相关丢失。

为了解决这个问题并遵循标准调用约定:
1.你已经有了序幕和尾声,这是节省$a0,这是好的。你应该简单地删除在尾声的$a0的恢复。
在两次调用fib之间:
1.您需要恢复原始的$a0,以便从中减去2,以便将参数传递给fib的第二次调用。这需要一条lw指令从堆栈存储器位置取入$a0,该指令保存在序言中(减法之前)。
1.您需要保存$v0(在对fib的两次调用之间),因为它保存了第一次调用的返回值,这将是执行正确加法所必需的。您可以将它保存到您刚刚加载$a0的同一个堆栈位置,因为通过分析函数的其余部分,您将不再需要原始参数值。
(You可以以相反的顺序执行项(2.)和(3.),但是这将需要额外的堆栈槽,因为在该顺序中,项(3.)不能保存到相同的堆栈槽,因为那里的值尚未为项(2.)恢复。

在第二次调用fib之后,需要将两个返回值相加:其中一个在$v0中,另一个已按照项目(3.)保存。因此,将(3.)中保存的值重新加载到任何可用的临时寄存器(即而不是$v0,比如$t0甚至$a0),并将其值相加为$v0

相关问题