MIPS汇编中的斐波那契数列

huangapple go评论63阅读模式
英文:

fibonacci sequence in mips assembly

问题

好的,这是您的代码的翻译部分:

所以,我已经尝试了两天来编写一个 MIPS 汇编程序,以准备我在几天后的考试,但是,我的大脑一直处于延迟状态,我不明白它应该如何工作以及我应该做什么,非常混乱。我编写的程序的所有版本都不会正确计算斐波那契数 unfortunately。

这是我目前的代码
.data
prompt: .asciiz "给我一个数字,我来找它的斐波那契数:"
result: .asciiz "斐波那契数是:"
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

希望这对您有帮助!

英文:

So, I have been trying for two days to write a program in mips assembly to exercise for my exams in a few days but yeah, my brain keeps lagging and I don't understand how it should work and what I should do, very confusing. All the versions of the programs I wrote would definitely not calculate the fibonacci unfortunately.

So this is my code as of now	
        .data
prompt: .asciiz &quot;Give me a number to find its fibonacci: &quot;
result: .asciiz &quot;The fibonacci is: &quot;
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 &lt; 2))
		beqz	$t0, fibB			# if (t0 == 0) / a0 &lt; 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
		

答案1

得分: 1

如果您选择使用自定义/非标准的调用约定,即保存和恢复$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 清零,然后调用函数,作为一种非标准的应急措施 —— $v0 不是函数的参数,此外,您的应急措施未应用于 fib 内部的调用站点。我们不应该依赖于 $v0 在函数进入时具有某个值:只需设置它,覆盖任何旧值,以将所需的值返回给调用者。

其次,在 fib 函数内部,您通常不遵循标准的调用约定,特别是关于函数内部对 fib 的第二次调用。

  • 您正在使用非标准的寄存器保存和恢复,$a0 不应该被恢复,调用者不应该依赖于被调用者恢复 $a0$a0 应该为您自己函数的好处而保存,但没有理由为了调用者的好处而恢复它。

  • 第一次对 fib 的调用的返回值(假设)在 $v0 中,但是您进行了第二次(递归)对 fib 的调用,这必然也将返回值放在 $v0 中,因此您不再可以访问第一次调用的返回值,它已丢失!您可以在调试中观察到这一点。(该非标准调用约定将恢复调用得到的 $a0,而不是您想要的原始参数值,这意味着它将在 $a0 中将 -1 值返回给您,但正确的解决方案是使用标准调用约定。)

  • 第一次对 fib 的调用会由您自己的代码将 $a0 减去 1,以进行调用。您可以通过仅调用 fib(2) 来调试此问题。

  • fib(..) + fib(..) 中,您遗漏了加法。您很可能之所以遗漏了这个,是因为您不知道要添加的内容(或者可能是由于替代设计),这是因为您没有遵循调用约定的要求,以及与第一次调用 fib 的返回值的丢失有关。

要修复这个问题并遵循标准调用约定:

  1. 您已经有保存 $a0 的序言和尾声,这很好。您应该简单地删除尾声中 $a0 的恢复。

在两次调用 fib 之间:

  1. 您需要恢复原始的 $a0,以便从中减去 2,以用作第二次调用 fib 的参数。这需要 lw 指令从堆栈内存位置加载到 $a0,该位置在序言中保存(在减 2 之前)。

  2. 您需要保存 $v0(在两次调用 fib 之间),因为它保存了第一次调用的返回值,将需要它来执行正确的加法。您可以将其保存到刚刚从中加载 $a0 的相同堆栈位置中,因为通过对函数的其余部分进行分析,您将不再需要原始参数值。

(您可以按照(2.) 和 (3.) 的顺序执行这两项,但这将需要额外的堆栈插槽,因为在该

英文:

If your choice is to use a custom/non-standard calling convention that both saves & restores $a0, and passes $v0 as a accumulator parameter, then you are well off the mainstream track.&nbsp; It is possible that your instructors are telling you to do this, but then you need to go to them for help, or at least obtain knowledge of this alteration.

IMHO, at best this approach can be considered a very advanced optimization technique (though flawed or incomplete), so there's little advantage to learning this before you know how normal calling should work.

I also see that you've attempted to move the addition part of the expression fib(...) + fib(...) into the base case &#8212; this is folly since it simply doesn't belong there, and is then missing from all the non-base cases, and the resulting function simply isn't a normal recursive fib().


That code is doing something like:

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

So, this is only going to do addition at the base case and not at every level.

Also, to be clear, there are bad examples to be found on the internet, of fib() using non-standard calling conventions that many educators would consider problematic, yet others don't even understand the issues.


The rest of this post will deal with standard calling convention and register usages.

First, your base case has a typo (or design flaw): add $v0, $a0, $v0.&nbsp; In the base case, you want to simply copy $a0 into $v0, not a sum of whatever happens to be in $v0 with $a0, i.e. return n;.

Always test your code with the smallest numbers first so, do fib(0), then fib)(1), then fib(2).&nbsp; However, you have cleverly cleared $v0 to 0 before invoking the function, as a non-standard band-aid &#8212; $v0 is not a parameter to the function, plus your band-aid is not being applied to the internal call sites within fib.&nbsp; We are not supposed to rely on $v0 having some value upon function entry: simply set it, overwriting any old value, to return a desired value to the caller.

Second, within the fib function, you are generally not following the standard calling convention with regard to the second invocation of fib inside the function.

  • You are using a non-standard register save & restore, $a0 should not be restored, and callers should not rely on it being restored by callee.&nbsp; $a0 should be saved for your own function's benefit but there is no reason to restore it for the caller's benefit.

  • The return value from the first invocation to fib is (supposedly) in $v0, yet you make a second (recursive) call to fib, which necessarily also returns its return value in $v0, and as a result you no longer have access to the first invocation's return value; it is lost!&nbsp; You can observe this in debugging.&nbsp; (That non-standard calling convention is going to restore the $a0 that the invocation got, not the original parameter value you want meaning it will return to you in $a0 the -1 value, however the proper solutions is to use the standard calling convention.)

  • The parameter $a0 is being wiped out by the first invocation to fib by your own code subtracting 1 from it in order to make that invocation=.&nbsp; You can observe this in debugging, with just fib(2).

  • You are missing the addition in fib(..) + fib(..).&nbsp; You are most likely missing this because you don't know what to add together (or perhaps due to the alternative design), which comes from not following the calling convention's requirements, and the associated loss of the first $v0 return value from the first invocation of fib.


In order to fix this and follow the standard calling convention:

  1. You already have prologue and epilogue that is saving $a0, which is good.&nbsp; You should simply remove the restoration of $a0 in epilogue.

In between the two invocation's of fib:

  1. You need to restore the original $a0 in order to subtract 2 from it for parameter passing to the second invocation of fib.&nbsp; This requires a lw instruction to fetch into $a0 from the stack memory location where it was saved in prologue (before the -2 subtraction).

  2. You need to save $v0 (in between the two invocations to fib), as it hold the return value from the first invocation, which will be needed to perform the proper addition.&nbsp; You can save it into the same stack location that you just loaded $a0 from, since by analysis of the remainder of the function, you'll no longer need the original parameter value.

(You could do items (2.) & (3.) in reverse order, but that would require an additional stack slot, since in that ordering item (3.) cannot save to the same stack slot, as the value there is not yet recovered for item (2.))

After the second invocation to fib, you need to add the two return values together: one of them is in $v0 and the other has been saved as per item (3.).&nbsp; So, reload the value saved in (3.) into any available temporary register (i.e. other than $v0, say $t0 or even $a0), and sum it's value to $v0.

huangapple
  • 本文由 发表于 2023年5月29日 23:32:24
  • 转载请务必保留本文链接:https://go.coder-hub.com/76358578.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定