我们有一个任务要把下面的C代码翻译成汇编代码:
#include <stdio.h>
#include <stdlib.h>
int gcd(int a, int b)
{
int ret;
while (a != b){
if (a > b){
ret = gcd(a - b, b);
return ret;
}
else{
ret = gcd(a , b - a);
return ret;
}
}
return a;
}
void main(void)
{
int a;
int b;
int c;
printf("a & b? ");
scanf(" %d %d",&a,&b );
c = gcd(a, b);
printf("result = %d",c);
}
它可以找到整数的最大公约数。我们必须将它转换为汇编
.data
str1: .ascii "result = \0"
str2: .ascii "\n\0"
.text
.global main
main:
addi sp,sp,-32
sw ra,28(sp)
sw s0,24(sp)
addi s0,sp,32
call read_int
mv s0,a0
call read_int
mv s1,a0
mv a0,s0 #a0 = a
mv a1,s1 #a1 = b
call gcd
mv s1,a0
la a0,str1
call print_string
mv a0,s1
call print_int
la a0,str2
call print_string
lw ra,28(sp)
lw s0,24(sp)
addi sp,sp,32
call show_pc
call exit
ret
gcd:
addi sp,sp,-8 # Increase stack w/ space for 1 int
sw s3,4(sp) # push S0 to stack
sw s4,4(sp)
L1: beq a0,a1,L2 # if (a0==a1) go to L2
slt s4,a0,a1 # if (a<b) s1=1, else s4=0
beq s4,zero,L3 # if s4==0 go to L3
sub s3,a0,a1 # varRet(s3) = a-b
call gcd # recursion
L3: sub s3,a1,a0 # varRet(s3) = b-a
call gcd # recursion
beq zero,zero,L1 # jump to L1
L2: lw s3,4(sp) # restore old s0
lw s4,4(sp)
addi sp,sp,4 # decrease stack
jr ra # jump to ra
但是我在我的gcd函数的代码中得到了一个数据保存错误。可能是因为我使用变量或结束函数的方式。
任何帮助理解如何做到这一点将不胜感激。
2条答案
按热度按时间368yc8dk1#
Erik的回答解释了代码中的错误。代码中有很多错误。下面我列出几个:
1.寄存器保存/恢复代码错误。从
4(sp)
进行存储/加载,因此一个寄存器被乱码1.恢复代码中的
addi
错误--它与保存代码不匹配1.没有保存/恢复返回地址(例如
ra
)。实际上,这是 * 唯一 * 需要保存/恢复的寄存器。a0/a1
(即a
和b
)在递归调用中从不更改1.通常,返回值的格式是
v0
,这只是惯例,所以没什么大不了的。1.我们只需要一个额外的寄存器来获取
slt
结果。按照惯例,这可以是一个t*
寄存器[不需要保存/恢复]。请注意,在下面的代码中,我测试了修改后的C函数,但没有测试asm。
当我想编写汇编程序时,我重写C代码,只使用
if
和goto
,以便更接近地模拟[建议的]汇编程序。if
只能是简单的(例如)if (a > b)
,* 不能是 *if ((a > b) && (c < d))
,后者必须拆分成简单的if
(使用更多的goto
)以下是重构的asm代码:
你最初的C代码结合了递归和循环的元素,这个函数实际上根本不需要是递归的。
下面是一个循环版本:
下面是汇编程序代码:
下面是我使用的完整测试C程序:
rdlzhqv92#
递归有点red herring。只要运行时体系结构支持(递归)调用栈- RISC V环境是这样做的-那么支持或执行递归就归结为一个函数A调用另一个函数B。(在递归的情况下,A和B是同一个函数。)重点是,你所要做的就是遵循 * 函数调用 * 的规则,递归就会简单地工作。
(通过偏离标准调用规则,可以优化函数调用,但这通常不是教师在这里要做的。)
我想谈以下几点:
s3
和s4
,这两个寄存器对于此函数是不必要的。(如果使用它们,需要修复prologue和epilog,因为s3
和s4
都保存到相同的存储器位置,所以最后保存的将获得存储器位置。)ret
,应使用call-crobbered寄存器,即a0
是一个很好的选择。t0
作为涉及关系测试的临时变量,但这是MIPS风格的编程:与MIPS不同,RISC V在条件分支中具有关系的全部补充,因此不需要slt
。a-b
,b
)作为参数传递给递归调用,第一个参数在a0
中,第二个参数在a1
中。在第二个调用中传递(a
,b-a
)时也是如此。ra
,因为它将自动重新用于后续调用,并且您将需要原始ra
值以返回到正确的调用方。while
循环编写的,但它实际上不包含循环。循环体中的每个路径都有一个return
语句,因此,“循环”要么从不运行,要么只运行一次-最好写成if
。程序集代码试图创建一个实际的循环,在循环中的所有代码路径上缺少return
语句的点。return
构造需要执行函数epilog(并返回给调用者),而不是落入碰巧是下一个代码的某个随机部分。