我正在尝试为函数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
我尝试在计算部分中将下一部分的地址加载到堆栈中,但是我无法弄清楚实现它是否正常工作。我是否需要"结束"堆栈两次?一旦它在当前和计算部分一次如何?我无法弄明白,任何帮助都会受到赞赏!
答案 0 :(得分:2)
非常好的努力。
回答你的问题:你可以在function
条目处建立一个堆栈框架,并在结束时从中恢复。
我确实需要稍微重新调整$s0
来存储一个中间值并将其添加到堆栈帧中的存储值(即堆栈帧现在是3个单词而不是2个单词)。
无论如何,这里是更正后的代码。我试图对它进行一些注释( IMO,在asm中,没有"太多评论" )[请原谅无偿的风格清理]:
.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程序员都没有意识到这一点。
在解释型语言(例如java
,python
等)中,y
的空格不会被保留直到 {{1}处的代码是执行。并且,当[{1}}进入&#34;超出范围时,他们[通常]会解除分配#34; [在point B
之后]。
大多数C程序员认为具有作用域声明[如y
]可以节省堆栈空间。 (即)在作用域块中,编译器会增加堆栈帧大小以容纳point C
,并在不再需要y
时再次缩小它。
这只是不正确。 C编译器将为所有函数作用域变量设置堆栈帧并保留空间,即使它们在函数后期或内部作用域[如y
]内声明。
这正是我们在y
中所做的。这是正确的,即使我们不需要/利用y
的堆栈空间[偏移0]直到函数的中间。
所以,我猜你用做动态[有效]保留空间的其他语言的经验或者关于C的常识,可能会让你进入一个更复杂的模型思想是必需的。因此,你原来的问题是:我必须&#34;结束&#34;堆栈两次?
我还应该提到function
的调用约定不符合 ABI。如果它是自包含的(即不调用任何其他内容),则完全罚款。也就是说,在asm中,对于&#34; leaf&#34;功能,我们可以定义我们想要的任何东西。
原因是 callee 会修改/删除$s0
次调用。我们从堆栈中保存/恢复它,但是调用另一个函数可能会搞砸了。补救措施只是使用另一个寄存器来保存值[或者保存/恢复到堆栈框架中的额外位置],如果function
最终调用其他内容,则会更多工作。