我們有一項任務是將以下 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 函式中的代碼中遇到了資料保存錯誤。可能是因為我如何使用變數或結束函式。
任何有關理解如何做到這一點的幫助將不勝感激。
uj5u.com熱心網友回復:
Erik 的回答解釋了代碼有什么問題。有許多錯誤。我列舉幾個:
- 注冊保存/恢復代碼錯誤。兩者都存盤/加載,
4(sp)因此一個暫存器被破壞 - 恢復代碼錯誤
addi——它與保存代碼不匹配 - 不保存/恢復回傳地址(例如
ra)。實際上,這是唯一需要保存/恢復的暫存器。 a0/a1(即a和b)對于遞回呼叫永遠不會改變- 通常,回傳值在
v0. 這只是慣例,所以沒什么大不了的。 - 我們只需要一個額外的暫存器來獲取
slt結果。按照慣例,這可以是一個t*暫存器[不需要保存/恢復]。
請注意,在下面的代碼中,我測驗了修改后的 C 函式,但沒有測驗 asm。
當我想撰寫匯編程式時,我會重寫 C 代碼以使用if并且goto更接近地模仿 [建議的] 匯編程式。
if只能是簡單的(例如)if (a > b)而不是 if ((a > b) && (c < d))。后者必須拆分為簡單if(使用 more goto)
int
gcd3(int a, int b)
{
int ret = a;
if (a == b)
goto done;
if (a > b)
goto a_gt_b;
a_lt_b:
ret = gcd(a, b - a);
goto done;
a_gt_b:
ret = gcd(a - b, b);
goto done;
done:
return ret;
}
這是重構的 asm 代碼:
gcd:
addi sp,sp,-4 # Increase stack w/ space for 1 int
sw ra,0(sp) # save return address
add v0,a0,zero # ret = a
beq a0,a1,done # if (a == b) we are done
slt t0,a0,a1 # is a > b?
beq t0,zero,a_gt_b # yes, fly
# a < b
a_lt_b:
sub a1,a1,a0 # b = b - a
call gcd # recursion
b done
# a > b
a_gt_b:
sub a0,a0,a1 # a = a - b
call gcd # recursion
b done
done:
lw ra,0(sp) # restore return address
addi sp,sp,4 # decrease stack
jr ra # return
您的原始 C 代碼結合了遞回和回圈的元素。該函式根本不需要遞回。
這是一個回圈版本:
int
gcd4(int a, int b)
{
loop:
if (a == b)
goto done;
if (a > b)
goto a_gt_b;
a_lt_b:
b = b - a;
goto loop;
a_gt_b:
a = a - b;
goto loop;
done:
return a;
}
這是匯編代碼:
gcd:
beq a0,a1,done # if (a == b) we are done
slt t0,a0,a1 # is a > b?
beq t0,zero,a_gt_b # yes, fly
# a < b
a_lt_b:
sub a1,a1,a0 # b = b - a
b gcd
# a > b
a_gt_b:
sub a0,a0,a1 # a = a - b
b gcd
done:
add v0,a0,zero # ret = a
jr ra # return
這是我使用的完整測驗 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;
}
int
gcd2(int a, int b)
{
int ret = a;
while (a != b) {
if (a > b) {
ret = gcd(a - b, b);
break;
}
else {
ret = gcd(a, b - a);
break;
}
}
return ret;
}
int
gcd3(int a, int b)
{
int ret = a;
if (a == b)
goto done;
if (a > b)
goto a_gt_b;
a_lt_b:
ret = gcd(a, b - a);
goto done;
a_gt_b:
ret = gcd(a - b, b);
goto done;
done:
return ret;
}
int
gcd4(int a, int b)
{
loop:
if (a == b)
goto done;
if (a > b)
goto a_gt_b;
a_lt_b:
b = b - a;
goto loop;
a_gt_b:
a = a - b;
goto loop;
done:
return a;
}
int
main(void)
{
int a;
int b;
int code = 0;
char buf[100];
while (1) {
printf("a & b? ");
fflush(stdout);
if (fgets(buf,sizeof(buf),stdin) == NULL)
break;
if (buf[0] == '\n')
break;
sscanf(buf," %d %d", &a, &b);
printf("%d %d\n", a, b);
int c1 = gcd(a, b);
printf("gcd1 = %d\n", c1);
int c3 = gcd3(a, b);
printf("gcd3 = %d\n", c3);
if (c3 != c1) {
printf("MISMATCH\n");
code = 1;
}
int c4 = gcd4(a, b);
printf("gcd4 = %d\n", c4);
if (c4 != c1) {
printf("MISMATCH\n");
code = 1;
}
if (code)
break;
}
return code;
}
uj5u.com熱心網友回復:
遞回有點像紅鯡魚。只要運行時架構支持(遞回)呼叫堆疊(RISC V 環境支持),那么支持或執行遞回歸結為與函式 A 呼叫另一個函式 B 相同。(在遞回情況下,A 和B 是同一個函式。)關鍵是你所要做的就是遵循函式呼叫的規則,遞回就會簡單地作業。
(有機會通過偏離標準呼叫規則來優化函式呼叫,但這通常不是講師想要的。)
我將解決以下問題:
使用暫存器
s3&s4,這對于這個函式是不必要的。(如果您確實使用它們,需要將序言和結語修復為兩者s3,并s4保存到相同的確切記憶體位置,因此最后保存的將贏得記憶體位置。)應該使用 call-clobbered register for
ret,iea0那里是個不錯的選擇。考慮
t0在涉及關系測驗的臨時工中使用可能,但這是一種 MIPS 編程風格:與 MIPS 不同,RISC V 在條件分支中具有完整的關系補充,因此slt不需要。必須正確地將 (
a-b,b) 作為引數傳遞給遞回呼叫,第一個 arg ina0,第二個 ina1。在第二次呼叫中傳遞 (a, ) 也是如此。b-a應該分配堆疊空間并保留
ra,因為它將自動重新用于后續呼叫,并且您需要原始ra值才能回傳給正確的呼叫者。程式集的控制流與 C 代碼不匹配。盡管 C 代碼是使用
while回圈撰寫的,但它實際上不包含回圈。回圈體內的每條路徑都有一個return陳述句,因此,“回圈”要么從不運行,要么只運行一次——最好寫成if. 匯編代碼試圖創建一個實際的回圈,但缺少回圈中return所有代碼路徑上的陳述句點。該return構造需要執行函式 Epilog(并回傳給呼叫者),而不是掉入恰好是下一個代碼的某個隨機部分。
轉載請註明出處,本文鏈接:https://www.uj5u.com/qukuanlian/515472.html
下一篇:裝配,在陣列中移動
