在我的上一篇博文中,我簡單的向大家介紹了匯編語言程序設計的三種基本方式。在一個程序中的不同地方,常常需要多次非循環的使用完成特定功能的程序段,這些程序段除了某些變量的賦值不同外,具有相同的指令序列,這時,我們為了減少重復編寫程序,縮短目標代碼,節省內存空間,把視線這一功能的指令序列組成一個相對獨立的程序段。這也就是我們這片文章中所要討論的子程序。
子程序相當於高級語言(比如C語言)中的過程和函數,在匯編語言中子程序也稱為過程。使用子程序的好處:
a、有利於程序模塊化、結構化和自頂向下的程序設計方法,簡化了程序設計過程。
b、增加了源程序的可讀性,便於調試維護
c、減少了目標代碼鎖占用的空間
d、子程序一旦編制成功,在開發研制各種軟件時都可使用,縮短了軟件的開發周期。
一、子程序的調用與返回
1、子程序的定義
子程序必須定義在一個邏輯段內,子程序的定義由過程定義偽指令PROC/ENDP來實現,它們分別用在程序的子程序的前後,一般格式如下:
PROC_NAME PROC [NEAR/FAR] ...... PROC_NAME ENDP
其中PROC_NAME為子程序名,也極為CALL的操作數,自程序具有3個屬性:段屬性、偏移量屬性和類型屬性,段屬性表示該子程序所在段的段基值。偏移量屬性表示該子程序在段中的偏移量。類型屬性也稱為距離屬性,可以是NEAR或FAR,屬性為NEAR的子程序只能在本段內調用,屬性為FAR的子程序則可以在本段以內以及其他段中調用。
2、調用指令
當主程序屬性是NEAR的子程序時,CPU把當前指令指針IP的內容壓入堆棧,作為返回地址保存起來,然後將子程序的偏移量送入IP,當從子程序返回時,將從堆棧彈出2個字節的返回地址送入IP,當調用屬性是FAR的過程時,CPU把當前的段寄存器CS與指令指針IP的內容都壓入堆棧,作為返回地址保存起來,然後將子程序的段基值與偏移量送入CS與IP,當子程序返回時,將從堆棧彈出4個字節的返回地址分別送入IP與CS。
我們容易知道,當主程序和子程序處於同一邏輯段時,可以把類型屬性定義為NEAR,也可以把類型屬性定義為FAR,然後進行調用。而當主程序與子程序不在同一邏輯段是,只可把過程的類型定義為FAR,然後調用。
二、返回指令
返回指令RET是子程序邏輯上的最後一條指令,也就是最後一條被執行的指令,它使子程序在完成功能後返回到調用它的CALL指令的後續指令處,即返回地址處繼續執行。
三、子程序設計的基本要求
1、子程序必須有一定的通用性
2、注意寄存器的保存和恢復
3、正確使用堆棧
4、選用適當的方法在主程序與子程序間進行參數傳遞
5、編制子程序說明信息文件
四、子程序與主程序間的參數傳遞
在匯編語言中最常用的參數傳遞方式有3種,分別是:用寄存器傳遞參數、用堆棧傳遞參數和用地址表達式傳遞參數。
1、用寄存器傳遞參數
這種方式是通過通用寄存器來傳遞的參數,即在主程序調用子程序前,將入口參數送到約定的通用寄存器中,子程序可以直接從這些寄存器中取出參數進行加工處理,並將結果放在約定的通用寄存器中,返回主程序,主程序再從約定的寄存器中取出結果,我們一例子來說明問題:
例:將兩個給定的二進制數(8位和16位)轉換為ASCII碼字符串。
分析:主程序提供呗轉換的數據和轉化後的ASCII碼字符串的存儲區的首地址。子程序完成二進制的轉換。為了提高子程序的代碼轉換通用性,它可以完成8位或16位數的轉換。設調用子程序時,入口參數為:被轉換的數在DX中,若位數小於16,則從高到低存放,轉換後的ASCII碼的存放首地址在DI中。下面給出一種實現方法:
DATA SEGMENT BIN1 DB 35H BIN2 DW 0AB48H ASCBUF DB 20H DUP (?) DATA ENDS STACK1 SEGMENT PARA STACK DW 20H DUP (0) STACK1 ENDS CODE SEGMENT ASSUME CS:CODE, DS:DATA, SS:STACK1 BEGIN: MOV AX, DATA MOV DS, AX XOR DX, DX LEA DI, ASCBUF ;存放ASCII碼的單元首地址送DI MOV DH, BIN1 ;待轉換的第一個數據送DH MOV AX, 8 ;待轉換的二進制數的位數送AX CALL BINASC MOV DX, BIN2 MOV AX, 16 LEA DI, ASCBUF ADD DI, 8 ;設置下一個數的存放首地址 CALL BINASC MOV AH, 4CH INT 21H BINASC PROC MOV CX, AX LOP: ROL DX, 1 ;最高位移入最低位 MOV AL, DL AND AL, 1 ;保留最低位,屏蔽其他位 ADD AL, 30H MOV [DI], AL ;存結果 INC DI ;修改地址指針 LOOP LOP RET BINASC ENDP CODE ENDS END BEGIN
2、用堆棧傳遞參數
這種方法是主程序先將入口參數壓入堆棧,子程序從堆棧中把參數讀出,進行加工處理。這裡要注意從堆棧中讀取數據與從堆棧中彈出數據是有區別的,從堆棧中讀取數據並不改變堆棧的棧頂指針SP,而從堆棧中彈出的數據,則需修改SP,在使用堆棧傳遞參數時,要保證堆棧狀態的正確。
我們還以上面的例子來說明下問題,這次采用堆棧傳遞參數
分析:如果使用堆棧,一般用包括:
a、在主程序中,將待轉換的數據、存放ASCII碼的首地址和轉換的位數壓入棧中
b、在子程序中保存信息
下面我們依然用程序說明問題,在程序的必要處我已經做了注釋
DATA SEGMENT BIN1 DB 35H BIN2 DW 0AB48H ASCBUF DB 20H DUP (?) DATA ENDS STACK1 SEGMENT PARA STACK DW 20H DUP (0) STACK1 ENDS CODE SEGMENT ASSUME CS:CODE, DS:DATA, SS:STACK1 BEGIN: MOV AX, DATA MOV DS, AX MOV AH, BIN1 PUSH AX ;待轉換數據壓棧 MOV AX, 8 PUSH AX ;待轉換位數壓棧 LEA DI, ASCBUF PUSH DI ;存放ASCII碼的首地址壓棧 CALL BINASC ;調用轉換子程序 MOV AX, BIN2 PUSH AX MOV AX, 10H PUSH AX ADD DI, 8 PUSH DI CALL BINASC MOV AH, 4CH INT 21H BINASC PROC PUSH AX PUSH CX PUSH DX PUSH DI MOV BP, SP MOV DI, [BP+10] ;從堆棧取出入口參數 MOV CX, [BP+12] MOV DX, [BP+14] LOP: ROL DX, 1 MOV AL, DL AND AL, 1 ADD AL, 30H MOV [DI], AL INC DI LOOP LOP POP DI POP DX POP CX POP AX RET 6 ;返回並從堆棧中彈出6個字節 BINASC ENDP CODE ENDS END BEGIN
3、用地址表傳遞參數
當要傳送的參數較多時,可在主程序中建立一個地址表,在調用子程序前,把所有參數的地址依次存放在該地址表中,然後把地址表的首地址通過寄存器傳送到子程序中去,而在子程序中,按照地址表中給出的地址逐個取出參數,用地址表傳遞參數的方法,在入口參數比較多時很方便,當返回參數較多時,可用同樣的方法傳遞參數,供主程序使用。
本文出自 “驿落黃昏” 博客,請務必保留此出處http://yiluohuanghun.blog.51cto.com/3407300/940566