assembly 递归fib函数,输出不正确,无法找到导致错误的原因

slhcrj9b  于 2023-06-30  发布在  其他
关注(0)|答案(1)|浏览(93)

我是一个初学者在汇编,我试图创建一个斐波那契函数。我试着调试我的代码,并以增量方式编写代码,确保它在我放入的每一行代码之后都能运行。通过这样做,我能够修复编译错误,但我不知道为什么我一直收到错误的输出,应该是13,而不是4(输入是7)。我不知道是什么原因造成的,我应该做些什么来解决它。下面是我在RV64I中编写的代码:

# main function

main:
    li x10, 7          # Load immediate value 7 into register x10
    jal ra, fib        # Jump and link to the fib function
    li x17, 10         # Load immediate value 10 into register x17
    ecall              # System call (end of program)

# fib function
fib:
    addi sp, sp, -16   # Allocate 16 bytes on the stack
    sd ra, 8(sp)       # Save the return address on the stack at offset 8
    sd x10, 0(sp)      # Save the value of x10 on the stack at offset 0
    bnez x10, elif     # Branch if x10 is not equal to zero

    li x10, 0          # Load immediate value 0 into register x10
    addi sp, sp, 16    # Deallocate 16 bytes from the stack
    jr ra              # Jump to the return address (end of function)

elif:
    li x28, 1          # Load immediate value 1 into register x28
    bne x10, x28, else # Branch if x10 is not equal to x28

    li x10, 1          # Load immediate value 1 into register x10
    addi sp, sp, 16    # Deallocate 16 bytes from the stack
    jr ra              # Jump to the return address (end of function)

else:
    addi x10, x10, -1  # Subtract 1 from the value of x10
    jal fib           # Jump and link to the fib function (commented out)
    mv x29, x10        # Move the value of x10 into register x29
 
    ld x10, 0(sp)      # Load the saved value of x10 from the stack
    addi x10, x10, -2  # Subtract 2 from the value of x10
    jal fib           # Jump and link to the fib function (commented out)
    add x10, x29, x10  # Add the value of x29 and x10, and store the result in x10

    ld ra, 8(sp)       # Load the saved return address from the stack
    addi sp, sp, 16    # Deallocate 16 bytes from the stack
    jr ra              # Jump to the return address (end of function)

下面是我试图在汇编中创建的fib函数的C++版本:

int fib(int n)
{ 
    if (n==0) return 0; 
    else if (n == 1) return 1;
    else return fib(n−1) + fib(n−2);
}

任何帮助将不胜感激。

kcwpcxri

kcwpcxri1#

对于许多情况,我会说运行调试器,看看出了什么问题。然而,当涉及到函数调用时,在一个函数调用上有很多需要注意的地方,你必须从两个Angular 来看待它--函数调用工作了吗(即函数调用是否有效)?step in并查看它的工作情况并提供正确的返回值),并且,* 函数调用是否正确地为调用者恢复了环境(即,寄存器值,包括堆栈指针)*,这更多的是来自调用者上下文的连续性的“跨越”概念。
要自己调试这种东西,您可能会发现fib(0)fib(1)fib(2)都可以工作(尽管fib(2)有点意外),而fib(3)不能,因此这就是调试的重点。这个想法是建立测试用例,从最简单的可能,当这些工作,稍微大一点。
函数调用是困难的,因为它的所有移动部件,而递归,尽管没有添加规则或普通函数调用之外的其他开销,但跟踪起来会很混乱,因为它的本质是会涉及多个堆栈帧。
您的寄存器使用没有遵循调用约定-但是,要明确的是,虽然代码应该遵循调用约定,但这不是特定的缺陷-问题是您将多个值放入x29并期望旧值神奇地重新出现,即使它们已经被删除。
寄存器是有效的(快速的)全局变量。因此,如果一个fib将x29设置为某个值,那么第二个递归调用也会这样做,第二个递归调用“获胜”,它会消除先前/更大递归调用的x29。x29中的值是最后设置它的人的值,当第一个递归调用被恢复时,这是您的代码所不期望的。
顺便说一句,你使用x寄存器编号而不是友好的寄存器名称是不可读的,而且你还混合了友好的名称(例如。ra)与x数字。如果你想写出可读的代码,坚持使用友好的寄存器名,避免使用x寄存器号(除了特殊的x 0)。
x29中寻找的是调用保留寄存器的概念。x29数字指的是t4,按照惯例,它不是一个保留调用的寄存器,您应该使用的是s寄存器之一,比如s0。然而,仅仅改变寄存器是不够的,因为使s寄存器保留调用的魔术是用代码完成的,您还需要添加代码。
要使s0工作,您需要额外的堆栈空间,并在序言和后记中保存和恢复s0。调用保留寄存器的想法是,你不知道你在保存和恢复什么,但你知道,如果你这样做,那么你就可以以一种在调用中保留的方式使用寄存器。
作为一个完全独立的替代方案,您可以避免s寄存器,只需将fib返回值保存到堆栈中。这也需要额外的堆栈空间字,但这次这个字在序言后记中不是用来保存/恢复的,而是简单地将第一个fib的值保存到堆栈中,然后在第二次调用fib后重新加载它。
使用这种方法,您可以在内存中保存稍后需要的(返回)值,在函数调用(第二个到fib)之后,堆栈内存将保留函数调用。这种使用内存的方法实际上比调用保留寄存器方法更简单,但仅适用于非常简单的函数,如递归fib。如果函数做了更多的工作(即有一个循环或多个函数调用),那么s寄存器的方法将成为一个净赢。
精明的读者可能会意识到,用于保存传入a0值的堆栈空间可以重用于存储第一个fib的返回值,这意味着该函数只需要两个(64位)字的堆栈空间。为了使其工作,在第一个fib调用之后,内存中的值需要在存储返回值之前重新加载到寄存器中。
这意味着在两个递归fib调用之间:

  • 将原始的a0值重新加载到a1-a1中,因为a0仍然保存稍后需要的返回值
  • a0(返回)值存储到该内存插槽中,现在不再需要该插槽
  • 将第二次fib调用的参数a1-2的值计算到a0中。

在第二次递归调用之后,重新加载堆栈保存的值以计算最终的返回值。

相关问题