在LC-3的編程中,有時候需要重複調用一些子程序,但是又不能像c語言一樣定義函數,但是LC-3提供了相當的機制供我們使用
TRAP機制調用子程序
在上一篇文章【LC-3簡易四子棋(深大計系1實驗4) 思路+代碼+詳細註釋】中,我們提到:
對於輸入輸出,我們通過一些“僞操作”進行“系統調用”來實現讀取字符,輸出字符,可是這些操作的本質是TRAP機制來完成的調用
TRAP調用
TRAP將目標中斷矢量(也就是trap的操作數,比如TRAP x21
操作數是0x21)的高位補0得到一個數x,然後去內存x處尋找下一條指令的地址,即將內存x處的值壓入PC寄存器中
比如TRAP x21
,內存x0021處,存的是子程序首地址,這個地址會被壓入PC
TRAP返回
函數都要返回,那麼TRAP會在進入子程序之前,將返回地址存到R7
- 在一個子程序的最後,必須使用
RET
指令,即JSRR R7
跳轉到R7寄存器對應的地址
這意味着我們不能夠在子程序裏面不經保存就修改R7的值,這樣會使得子程序無法返回,上一篇文章提到的【在一個子程序裏不保存R7就調用另一個子程序,最終結果不正確】就是因爲這個問題導致的
因爲TRAP的操作數只有8位,所以支持256個子程序
例子 TRAP x21 (系統函數)輸出字符源碼
在LC-3的系統函數中,x21負責輸出一個字符,字符的值存放在R0
我們可以看看它的源碼,首先找到0x0021內存處,查看真正的起始地址,可以看到是0x0430
我們跳轉到0x0430查看源碼
可以看到,子程序調用第一步就是保存要用的寄存器的值,然後開始讀取KBSR鍵盤輸入標誌位,死循環直到鍵盤輸入標誌位爲1,之後讀取數據到R0,然後RET,注意所有子程序都要RET
模擬棧
方法介紹
因爲LC-3這種調用機制,使得函數只能被調用一層,多層調用需要不斷的保存返回地址,而R7將會被覆蓋!
因此,遞歸就難以實現了,但是我們可以通過模擬一個棧,來實現保存函數的參數,返回地址,返回值,來實現遞歸函數
我們將要實現一個這樣的函數來計算等差數列的前n項和
int func(int x)
{
if(x==0) return 0;
return x+func(x-1);
}
棧指針
我們使用一個地址標號sp來作爲棧指針,sp裏面存的值表示棧頂在內存中的位置,約定sp永遠指向第一個空餘的棧位置,如圖所示:
存取數據
如上,因爲棧指針的特殊結構,即【指向第一個空閒的棧位置】,我們讀取棧頂數據的時候就要小心:
存數據:
- 數據存入棧指針指向的內存
- 棧指針
++
取數據:
1.棧指針--
2.取棧指針指向的內存的數據
函數棧幀結構
我們在函數調用之前,分別壓入返回地址,形參x,然後函數處理,彈出形參和返回地址,然後把返回值壓棧
一次遞歸調用需要的語句
一次調用函數需要以下語句
- 返回地址壓棧
- 形參壓棧
- 棧指針+2
- 遞歸調用
- 從棧中彈出返回值
彙編代碼
.ORIG x3000
LD R0, sp ; 取棧指針到R0
LEA R1, EOC ; R1讀取返回地址
AND R2, R2, #0
ADD R2, R2, #10 ; R2載入形參10
STR R1, R0, #0 ; 返回地址壓棧
STR R2, R0, #1 ; 形參壓棧
ADD R0, R0, #2 ; 棧指針++
ST R0, sp ; 存棧指針
BRnzp func ; 調用
EOC LD R0, sp ; 取棧指針到R0
LDR R2, R0, #-1 ; R2存返回值
ADD R0, R0, #-1
ST R0, sp ; 存棧指針
TRAP x25 ; 程序結束
func LD R0, sp ; 取棧指針到R0
LDR R1, R0, #-1 ; 取棧中的值即參數1到R1
BRz ret0 ; 如果x=0返回0
BRnzp call ; 否則遞歸
ret0 AND R2, R2, #0
ADD R2, R2, #0 ; R2=0
BRnzp retv
call LD R0, sp
LEA R2, call
ADD R2, R2, #10 ; R2存返回地址
STR R2, R0, #0 ; 返回地址壓棧
ADD R0, R0, #1 ; 棧指針++
ADD R3, R1, #-1 ; R3=R1-1
STR R3, R0, #0 ; 參數R3壓棧
ADD R0, R0, #1 ; 棧指針++
ST R0, sp ; 指針存回去
BRnzp func ; 遞歸調用x-1
LD R0, sp ; 遞歸返回地點
STR R2, R0, #-1 ; 讀取棧中的返回值到R2
ADD R0, R0, #-1 ; 彈出返回值
ST R0, sp ; 存棧指針
; 將R2作爲返回值壓棧
retv LD R0, sp ; 取棧指針到R0
LDR R1, R0, #-1 ; 取參數1到R1
LDR R3, R0, #-2 ; 取返回地址到R3
ADD R2, R2, R1 ; return x+func(x-1)
STR R2, R0, #-2 ; 返回值壓棧
ADD R0, R0, #-1
ST R0, sp ; 存棧指針
JSRR R3 ; 返回
sp .FILL 0x4396
.END