我正在完成一项任务,并且很难理解如何在C中正确编码以下问题。
int choose(int n, int k){
if (k == 0) {
return 1;
} else if (n == k) {
return 1;
} else if (n < k) {
return 0;
} else {
return choose(n-1, k-1) + choose(n-1, k);
}
}
我的想法是使用三个寄存器将值存储到堆栈中,每次调用$s0, $s1, $s2
,其中$s0
将包含更新的n
的值; $s1
会维持k
的价值;并且$s2
会在第二个k
中保留choose(n-1, k)
的值,因为该值只会在父级调用更改时减少。我选择这个的原因是因为k
的值没有从这个中的每次调用中减去,它应该是相同的,直到父级在前一次调用中减少它。
这是我正在尝试的Choose
程序。问题是我当然没有得到正确的答案。
Choose:
#store current values onto stack
addi $sp, $sp, -16
sw $ra, 0($sp)
sw $s0, 4($sp)
sw $s1, 8($sp)
sw $s2, 12($sp)
#check values meet criteria to add to $v0
beq $s1, $0, one
beq $s0, $s1, one
blt $s0, $s1, zero
beq $s2, $0, one
#no branches passed so decrement values of n and k
subi $s0, $s0, 1
subi $s1, $s1, 1
#move values of registers to $a's for argument passing
move $a0, $s0
move $a1, $s1
jal Choose #call procedure again
#this is where I'm having my problems
#not sure how to loop the procedure to get
#the second half of the equation Choose(n-1,k)
#which is the reason for the next 2 lines of code
move $a2, $s2
jal Choose
add $v0, $v0, $v1
j Choose_Exit
#add one to $v1 from branch passed
one:
addi $v1, $v1, 1
j Choose_Exit
#branch returns 0
zero:
addi $v1, $v1, 0
j Choose_Exit
#return values to caller from stack
Choose_Exit:
lw $s2, 12($sp)
lw $s1, 8($sp)
lw $s0, 4($sp)
lw $ra, 0($sp)
addi $sp, $sp, 16
jr $ra
所以我在理解如何正确实现此递归过程两次以将它们添加到一起时遇到问题。我可以理解如何在MIPS中创建递归过程来执行阶乘,因为这始终是任何语言的递归定义。但是使用不同参数的递归然后将它们全部加在一起让我感到困惑。
当写在纸上时,我知道这个程序可以用父母和孩子的二叉树来表示。父项是单个函数Choose(n,k)
,子项是Choose(n-1, k-1) + Choose(n-1, k)
,一旦叶子项中的一个从if
语句分支,它会将一个数字传递给将等待另一个的父项添加的被调用者部分返回其值等等
任何帮助指出我正确的方向,我的方法是错误的将是伟大的。我理解一开始,我理解结束,只需要一些帮助来帮助理解中间最重要的部分。
答案 0 :(得分:2)
你非常接近。
您建立了包含四个单词的堆栈框架:返回地址,arg1,arg2,并保存为返回值。
你的主要障碍是,在第一次调用你的函数之后,你必须将$v0
保存到堆栈中[如上面提到的Margaret]。
这里有一些我认为可行的代码。它与你的非常相似,但我是从头开始编写的。它有正确的&#34; push&#34; /&#34; pop&#34;第一个电话的返回值。
我确实为早期转义[非递归]情况添加了一个小优化:它们省略了创建堆栈框架。
无论如何,这是:
#@+
# int
# choose(int n, int k)
# {
#
# if (k == 0)
# return 1;
#
# if (n == k)
# return 1;
#
# if (n < k)
# return 0;
#
# return choose(n - 1,k - 1) + choose(n - 1,k);
# }
#@-
.text
# choose -- choose
#
# RETURNS:
# v0 -- return value
#
# arguments:
# a0 -- n
# a1 -- k
#
# registers:
# t0 -- temp for 1st return value
choose:
beqz $a1,choose_one # k == 0? if yes, fly
beq $a0,$a1,choose_one # n == k? if yes, fly
blt $a0,$a1,choose_zero # n < k? if yes, fly
# establish stack frame (preserve ra/a0/a1 + space for v0)
sub $sp,$sp,16
sw $ra,12($sp)
sw $a0,8($sp)
sw $a1,4($sp)
addi $a0,$a0,-1 # get n - 1 (common to both calls)
# choose(n - 1,k - 1)
addi $a1,$a1,-1 # get k - 1
jal choose
sw $v0,0($sp) # save 1st return value (on _stack_)
# choose(n - 1,k)
addi $a1,$a1,1 # get k (from k - 1)
jal choose
lw $t0,0($sp) # "pop" first return value from stack
add $v0,$t0,$v0 # sum 1st and 2nd values
# restore from stack frame
lw $ra,12($sp)
lw $a0,8($sp)
lw $a1,4($sp)
add $sp,$sp,16
jr $ra # return
choose_one:
li $v0,1
jr $ra
choose_zero:
li $v0,0
jr $ra
<强>更新强>
首先,我喜欢你在调用它之前如何记录过程。我要去偷那个!
做我的客人!这是多年写作asm的原因。有关如何写好asm的想法的入门读物,请参阅我的回答:MIPS linked list
我试过这个并且它有效。我需要尝试使用你的代码来理解为什么堆栈被操作(总是认为它必须在proc的开头和结尾)。
通常,堆栈帧是在proc启动时建立并从proc端恢复。您处理&#34;快速逃脱的代码&#34;基于已经建立框架,[非递归]案例 是正确的。
这只是一个小优化。但是,它来自这样一个事实:因为mips有很多寄存器,对于小功能,我们甚至不需要堆栈帧,特别是如果函数是一个&#34; leaf&#34;或&#34;尾巴&#34; (即它不会调用任何其他功能)。
对于较小的[非递归]函数,有时我们可以使用仅保留$ra
的单字堆栈帧(例如):fncA
调用fncB
,但{{} 1}}是一片叶子。 fncB
需要一个框架,但fncA
不。事实上,如果我们控制这两个函数并且知道 fncB
返回地址而不是在fncB
中创建堆栈帧:
$t9
通常情况下,我们无法依赖fncA
保留fncA:
move $t9,$ra # preserve return address
jal fncB # call fncB
jr $t9 # return
fncB:
# do stuff ...
jr $ra # return
,因为根据mip ABI,fncB
可以随意修改/删除任何1} em>注册不 $t9
或fncB
。但是,如果我们制定功能,我们认为$sp
是&#34;私人&#34;到$s0-$s7
(例如只有fncB
可以访问的C fncA
函数),我们可以 我们想要的任何。
信不信由你,以上的static
符合 ABI。
给定的被调用者(例如fncA
)不需要保留fncA
为其来电者< / em>,仅为自己。而且,重要的是fncA
内的值,而不是特定的寄存器。被调用者只需要保留$ra
,确保$ra
在退出时具有与条目相同的值,并且它返回到调用者[{1}}所执行的正确地址 - 因为如果调用了$s0-$s7
,则它具有$sp
中的值。
我喜欢你使用temp寄存器。
额外的寄存器是必需的,因为在mips中,我们不可以从内存操作数执行算术运算。 mips只能jr $t9
。 (即) no 这样的事情:
$ra
我使用fncA
来简化/清晰,因为当您需要临时注册时,lw/sw
是常用的。代码&#34;读得更好&#34;使用 add $v0,$v0,0($sp)
时。但是,这只是 约定。
在mip ABI中,$t0
可以修改,$t0-$t9
也可以修改,因为只需要保留$t0
。而且,&#34;修改&#34;意味着它们可用于保留任何价值或用于任何目的。
在上面的链接中,请注意$a0-$a3
直接递增$v1
以查找字符串的结尾。它使用$s0-$s7
作为一个有用的目的,但就其来说,strlen
正在被删除&#34; [由$a0
]。此用法符合 ABI。
在$a0
中,我本可以使用任何注册:$a0
,strlen
而不是choose
。实际上,在$v1
中的特定点,不再需要$a2-$a3
,因此可以使用它代替$t0
。虽然对于choose
,我们非 -ABI符合(因为我们保存/恢复$a0
),这可以在$t0
中使用,因为我们会恢复原始值来自函数epilog [stack frame pop]的choose
,保留了函数的递归性质。
正如我所说,$a0-$a1
是用于临时空间的常用寄存器。但是,我已经编写了使用所有10个函数的函数,并且仍然需要更多函数(例如使用Bresenham圆算法绘制到帧缓冲区中)。 choose
和$a0
可用作临时注册以获得额外的6.如果需要,$t0-$t9
可以保留在堆栈框架中,仅用于释放它们以用作更多临时注册表
答案 1 :(得分:0)
免责声明:我重写汇编代码没有检查它。
汇编代码:
#@+
# int
# choose(int n, int k)
# {
#
# if (k == 0)
# return 1;
#
# if (n == k)
# return 1;
#
# if (n < k)
# return 0;
#
# return choose(n - 1,k - 1) + choose(n - 1,k);
# }
#@-
.text
# choose -- choose
#
# RETURNS:
# v0 -- return value
#
# arguments:
# a0 -- n
# a1 -- k
#
# registers:
# t0 -- temp for local result to accumulate into v0
choose:
j choose_rec
addui $v0, $zr, 0 # initialize $v0 to 0 before calling
choose_rec:
beqz $a1,choose_one # k == 0? if yes, fly
addui $t0,$zr,1 # branch delay-slot with $t0 = 1
beq $a0,$a1,choose_one # n == k? if yes, fly
nop # branch delay-slot with $t0 = 1 already done
blt $a0,$a1,choose_zero # n < k? if yes, fly
addui $t0,$zr,0 # branch delay-slot with $t0 = 0
# establish stack frame (preserve ra/a0/a1)
sub $sp,$sp,12
addui $a0,$a0,-1 # get n - 1 (common to both calls)
sw $ra,8($sp)
sw $a0,4($sp)
jal choose_rec # choose(n - 1,k)
sw $a1,0($sp)
# restore from stack frame
lw $ra,8($sp)
lw $a0,4($sp)
lw $a1,0($sp)
add $sp,$sp,12
# choose(n - 1,k - 1)
j choose_rec # tail call: jump instead of call
addi $a1,$a1,-1 # get k - 1
choose_one:
choose_zero:
jr $ra # return
addui $v0,$v0,$t0 # branch delay-slot with $v0 += $t0