以下是以 MIPS 表示的斐波那契值。
fib: addi $sp, $sp, -24
sw $ra, 16($sp)
sw $a0, 20(sp) # recursive calls will overwrite original $a0
sw $s0. 0($sp) # holds fib(n-1)
# end prologue
slti $t0, $a0, 4 # fib(i) = i for i = 1, 2, 3; fib(0) = 0 by C code
beq $t0, $zero, L1
addi $v0, $a0, 0 # see prior comment (assumes $a0 non-negative integer)
j exit
# fib(n) = fib(n-1) fib(n-2)
L1: addi $a0, $a0, -1
jal fib
addi $s0, $v0, 0 # $s0 = fib(n-1) <-----how can use $v0?
addi $a0, $a0, -1
jal fib # upon return, $v0 holds fib(n-2)
add $v0, $v0, $s0
exit: # unwind stack and return
lw $s0, 0($sp)
lw $a0, 20($sp)
lw $ra, 16($sp)
addi $sp, $sp, 24
jr $ra
但是這里有一點我不太明白。據我所知,$s函式結束時暫存器以外的暫存器的值都會消失。
查看 fib 函式,何時n是1or 0,1or0存盤在 的值中$v0。之后,當函式結束時,不$v0應該洗掉的值嗎?所以,在呼叫之前fib(n-2),fib(n-1)洗掉了的回傳值,所以我認為要保存的代碼$s應該寫在fib函式中。
但是,在上面的代碼中,fib(n-1)函式的回傳值被下一個fib(n-2)函式使用。我不知道這怎么可能。
未保留的暫存器究竟保留了多長時間,何時將被洗掉?
uj5u.com熱心網友回復:
據我所知,$s以外的暫存器的值會在函式結束時消失。
所有暫存器都是永久性的,并且程式中的所有機器代碼都可以訪問,例如,包括 $t、$a、$v 和 $s 暫存器。
暫存器值僅通過執行針對它們的機器代碼指令而改變。是否允許(或應該)這樣做是軟體約定的問題,它告訴我們如何在可能具有數千個功能的程式中共享僅僅 31 個暫存器。
根據軟體協議,$a0用于傳遞第一個(整數或指標)引數,$v0用于回傳函式的(整數或指標)回傳值。設定暫存器后,除非某些機器代碼指令更改它,否則它不會更改。因此,暫存器值具有連續性并且完全受機器代碼程式的控制。
uj5u.com熱心網友回復:
請注意,fib您參考的內容遵循自定義和非標準呼叫約定。我不推薦它來學習呼叫保留與呼叫破壞暫存器。
在本節中:
L1: addi $a0, $a0, -1
jal fib
addi $s0, $v0, 0 # $s0 = fib(n-1) <-----how can use $v0?
addi $a0, $a0, -1 <------------- **HERE** --------
jal fib # upon return, $v0 holds fib(n-2)
在標記為 的行中**HERE**,代碼期望$a0在函式呼叫中幸存下來。這是對正確 MIPS 呼叫約定的非標準和虛偽的說明。
雖然此代碼有效,但它不遵循 MIPS 呼叫約定暫存器用法。它進行了自定義更改,只有知道呼叫方和被呼叫方的實作才能實作。對于一般情況,這種想法是錯誤的。
exit: # unwind stack and return
lw $s0, 0($sp)
lw $a0, 20($sp) <----------- **ALSO**
lw $ra, 16($sp)
addi $sp, $sp, 24
jr $ra
標記的這部分**ALSO**是$a0 為呼叫者恢復代碼的地方! 這是非常規的,在一般情況下不值得依賴。一般情況下,沒有人依賴于$a0被保存,所以沒有人應該費心去恢復它。
這是 recursive 的正確(且更有效)版本fib:
fib:
beq $a0, $0, return0 # goto return 0 if n == 0
li $v0, 1 # load $v0 with 1 for comparison and return
beq $a0, $v1, return # if n == 1 return leaving 1 in $v0
addiu $sp, $sp -8 # allocate two words of stack space
sw $ra, 4($sp) # save $ra
sw $a0, 0($sp) # save n
addi $a0, $a0, -1
jal fib # fib(n-1)
lw $a0, 0($sp) # restore $a0 for next call to fib
sw $v0, 0($sp) # store return value of fib(n-1) in stack
addi $a0, $a0, -2
jal fib # fib(n-2)
lw $t0, 0($sp) # reload return value from fib(n-1)
add $v0, $t0, $v0 # fib(n-1) fib(n-2)
lw $ra, 4($sp) # restore our return address
addiu $sp, $sp, 8 # release stack
jr $ra # and use return address
return0:
li $v0, 0
return:
jr $ra
這個版本實際上遵循 MIPS 呼叫約定。它不會轉換$a0為呼叫保留暫存器,而是$a0在第二次遞回呼叫之前重新加載,而不是在結尾處重新加載。
This version does not use $s registers, as they are actually a disadvantage in the circumstances of this particular function. Stack memory is used instead. An $s register version would be (just slightly) longer, because the same number of loads & stores would be involved (but for different purpose of preserving $s register) though a extra register-to-register copy instruction would be needed making it longer.
轉載請註明出處,本文鏈接:https://www.uj5u.com/caozuo/328828.html
下一篇:Git子樹最大函式遞回深度
