LC-3 子程序調用與模擬棧調用遞歸函數

在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. 棧指針++
    在這裏插入圖片描述

取數據:

1.棧指針--
2.取棧指針指向的內存的數據

在這裏插入圖片描述

函數棧幀結構

我們在函數調用之前,分別壓入返回地址,形參x,然後函數處理,彈出形參和返回地址,然後把返回值壓棧
在這裏插入圖片描述

一次遞歸調用需要的語句

一次調用函數需要以下語句

  1. 返回地址壓棧
  2. 形參壓棧
  3. 棧指針+2
  4. 遞歸調用
  5. 從棧中彈出返回值

彙編代碼

.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

運行結果

在這裏插入圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章