一、子程序結構
- 子程序=函數=過程
- 子程序指令
- 子程序調用指令
-
CALL LABEL ;調用標號、子程序名指定的子程序
- 分成近調用(段內調用)和遠調用(段間調用)
- 入棧返回地址:將CALL下條指令的地址壓入堆棧
- 近調用:IP入棧
- 遠調用:CS和IP都入棧
-
- 子程序返回指令
-
RET - 分為有參數返回和無參數返回,都是出棧返回地址
-
- 過程定義偽指令
-
過程名 PROC ………… ………… 過程名 ENDP過程定義應該書寫於.EXIT和END之間;也可以安排在主程序開始執行的第一條語句之前
- PROC后面可以加參數:NEAR/FAR
-
- 子程序調用指令
- 子程序設計
- 利用過程定義偽指令聲明
- 最后利用RET返回主程序,主程序執行CALL指令調用子程序
- 壓入彈出成對使用使堆棧保持平衡
- 子程序開始時保護用到的寄存器內容,返回前逆序彈出恢復到原來的寄存器中。
- 安排在代碼段的主程序之外
- 允許嵌套和遞歸
- 例:回車換行子程序
-
;eg502.asm .model small .stack .data .code .startup call dispcrlf .exit dispcrlf proc ;回車換行子程序 push ax ;保護寄存器 push dx mov dl,0dh ;輸出回車字符 mov ah,2 int 21h mov dl,0ah ;輸出換行字符 mov ah,2 int 21h pop dx ;恢復寄存器 pop ax ret ;子程序返回 dispcrlf endp end
-
二、參數傳遞
- 傳遞參數的多少反應程序模塊間的耦合程度
- 傳遞的內容:
- 數據本身
- 數據的存儲地址
- 傳遞方法:
- 寄存器
- 變量
- 堆棧
- 寄存器傳遞參數
- 把參數存於約定的寄存器
- 少量數據直接傳遞數值
- 大量數據只能傳遞地址
- 帶有出口參數的寄存器不能保存和恢復
- 使用低八位寄存器時不保證不影響高八位數據
- 例:顯示一個數據的數值(數值和ASC碼的轉換)
-
;eg410.asm .model small .stack .data STRING DB 'AX=',4 DUP(?),'H','$' .CODE .startup MOV AX,123AH MOV CX,4 XOR BX,BX AGAIN: ROL AX,1 ROL AX,1 ROL AX,1 ROL AX,1 PUSH AX CALL HTOASC MOV STRING+3[BX],AL INC BX POP AX LOOP AGAIN MOV AH,9 MOV DX,OFFSET STRING INT 21H .exit HTOASC PROC AND AL,0FH OR AL,30H CMP AL,'9' JBE ABCD ADD AL,7 ABCD: RET HTOASC ENDP end
-
- 有符號十進制整數的顯示
-
;eg504.asm .model small .stack .data array dw 6789,-1234,0,1,-9876,32767,-32768,5678,-5678,9000 count = lengthof array .code .startup mov cx,count xor bx,bx again: mov ax,array[bx] ;將AX=入口參數 call dispsiw ;調用子程序,顯示一個數據 add bx,type array call dispcrlf ;光標回車換行以便顯示下一個數據 loop again .exit dispsiw proc ;顯示有符號十進制數的通用子程序 push ax ;入口參數:AX=欲顯示的數據(補碼) push bx push dx test ax,ax ;判斷數據是零、正數或負數 jnz dsiw1 mov dl,'0' ;是零,顯示“0”后退出 mov ah,2 int 21h jmp dsiw5 dsiw1: jns dsiw2 ;是負數,顯示“-” mov bx,ax ;AX數據暫存於BX mov dl,'-' mov ah,2 int 21h mov ax,bx neg ax ;數據求補(絕對值) dsiw2: mov bx,10 push bx ;10壓入堆棧,作為退出標志 dsiw3: cmp ax,0 ;數據(商)為零,轉向顯示 jz dsiw4 xor dx,dx ;擴展被除數DX.AX div bx ;數據除以10:DX.AX÷10 add dl,30h ;余數(0~9)轉換為ASCII碼 push dx ;數據各位先低位后高位壓入堆棧 jmp dsiw3 dsiw4: pop dx ;數據各位先高位后低位彈出堆棧 cmp dl,10 ;是結束標志10,則退出 je dsiw5 mov ah,2 ;進行顯示 int 21h jmp dsiw4 dsiw5: pop dx pop bx pop ax ret ;子程序返回 dispsiw endp dispcrlf proc ;回車換行子程序 push ax ;保護寄存器 push dx mov dl,0dh ;輸出回車字符 mov ah,2 int 21h mov dl,0ah ;輸出換行字符 mov ah,2 int 21h pop dx ;恢復寄存器 pop ax ret ;子程序返回 dispcrlf endp end
-
- 把參數存於約定的寄存器
- 變量傳遞參數
- 子程序和主程序如果在同一模塊不需要特殊說明
- 如果不在同一個模塊需要利用PUBLEC EXTREN說明
- 例:
- 二進制輸入程序
;eg505.asm .model small .stack .data count = 5 array dw count dup(0) temp dw ? ;共享變量 .code .startup mov cx,count mov bx,offset array again: call readbw ;調用子程序,輸入一個數據 mov ax,temp ;獲得出口參數 mov [bx],ax ;存放到數據緩沖區 add bx,type array call dispcrlf ;光標回車換行以便輸入下一個數據 loop again .exit readbw proc ;二進制輸入子程序 push ax ;出口參數:共享變量TEMP push bx push cx rdbw1: xor bx,bx ;BX用於存放二進制結果 mov cx,16 ;限制輸入字符的個數 rdbw2: mov ah,1 ;輸入一個字符 int 21h cmp al,'0' ;檢測鍵入字符是否合法 jb rderr ;不合法則返回重新輸入 cmp al,'1' ja rderr sub al,'0' ;對輸入的字符進行轉化 shl bx,1 ;BX的值乘以2 or bl,al ;BL和AL相加 loop rdbw2 ;循環鍵入字符 mov temp,bx ;把BX的二進制結果存放TEMP返回 pop cx pop bx pop ax ret rderr: push ds ;保護DS mov ax,cs ;因信息保存在代碼段,所以需要設置DS=CS mov ds,ax lea dx,errmsg ;顯示錯誤信息 mov ah,9 int 21h pop ds ;恢復DS jmp rdbw1 errmsg db 0dh,0ah,'Input error, enter again: $' readbw endp dispcrlf proc ;回車換行子程序 push ax ;保護寄存器 push dx mov dl,0dh ;輸出回車字符 mov ah,2 int 21h mov dl,0ah ;輸出換行字符 mov ah,2 int 21h pop dx ;恢復寄存器 pop ax ret ;子程序返回 dispcrlf endp end
- 有符號十進制數輸入程序
;eg506.asm .model small .stack .data count = 5 array dw count dup(0) temp dw ? ;共享變量 .code .startup mov cx,count mov bx,offset array again: call readsiw ;調用子程序,輸入一個數據 mov ax,temp ;獲得出口參數 mov [bx],ax ;保存到數據緩沖區 add bx,2 call dispcrlf ;分行 loop again .exit readsiw proc ;輸入有符號十進制數的通用子程序 push ax ;出口參數:變量TEMP=補碼表示的二進制數值 push bx ;說明:負數用“-”引導 push cx xor bx,bx ;BX保存結果 xor cx,cx ;CX為正負標志,0為正,-1為負 rsiw0: mov ah,1 ;輸入一個字符 int 21h cmp al,'+' ;是“+”,繼續輸入字符 jz rsiw1 cmp al,'-' ;是“-”,設置-1標志 jnz rsiw2 mov cx,-1 rsiw1: mov ah,1 ;繼續輸入字符 int 21h rsiw2: cmp al,'0' ;不是0~9之間的字符,則輸入數據結束 jb rsiw3 cmp al,'9' ja rsiw3 sub al,30h ;是0~9之間的字符,則轉換為二進制數 xor ah,ah ;AL零位擴展為AX shl bx,1 ;利用移位和加法實現數值乘10:BX←BX×10 mov dx,bx ;參見例3-8 shl bx,1 shl bx,1 add bx,1 add bx,ax ;已輸入數值乘10后,與新輸入數值相加 jmp rsiw1 ;繼續輸入字符 rsiw3: cmp cx,0 ;是負數,進行求補 jz rsiw4 neg bx rsiw4: mov temp,bx ;設置出口參數 pop cx pop bx pop ax ret ;子程序返回 readsiw endp dispcrlf proc ;回車換行子程序 push ax ;保護寄存器 push dx mov dl,0dh ;輸出回車字符 mov ah,2 int 21h mov dl,0ah ;輸出換行字符 mov ah,2 int 21h pop dx ;恢復寄存器 pop ax ret ;子程序返回 dispcrlf endp end
- 二進制輸入程序
- 堆棧傳遞參數
- 注意PUSH后不能是常數
- 例子:
- 計算有符號數的平均值程序
;eg507.asm .model small .stack .data array dw 675, 354, -34, 198, 267, 0, 9, 2371, -67, 4257 .code .startup mov ax,lengthof array push ax ;壓入數據個數 mov bx,offset array push bx ;壓入數組的偏移地址 call mean ;調用求平均值子程序,出口參數:AX=平均值(整數部分) add sp,4 ;平衡堆棧(壓入了4個字節數據) call dispsiw ;顯示 .exit mean proc ;計算16位有符號數平均值子程序 push bp ;入口參數:順序壓入數據個數和數組偏移地址 mov bp,sp ;出口參數:AX=平均值 push bx ;保護寄存器 push cx push dx mov bx,[bp+4] ;BX=堆棧中取出的偏移地址 mov cx,[bp+6] ;CX=堆棧中取出的數據個數 xor ax,ax ;AX保存和值 mean1: add ax,[bx] ;求和 add bx,type array ;指向下一個數據 loop mean1 ;循環 cwd ;將累加和AX符號擴展到DX idiv word ptr [bp+6] ;有符號數除法,AX=平均值(余數在DX中) pop dx ;恢復寄存器 pop cx pop bx pop bp ret mean endp dispsiw proc ;顯示有符號十進制數的通用子程序 push ax ;入口參數:AX=欲顯示的數據(補碼) push bx push dx test ax,ax ;判斷數據是零、正數或負數 jnz dsiw1 mov dl,'0' ;是零,顯示“0”后退出 mov ah,2 int 21h jmp dsiw5 dsiw1: jns dsiw2 ;是負數,顯示“-” mov bx,ax ;AX數據暫存於BX mov dl,'-' mov ah,2 int 21h mov ax,bx neg ax ;數據求補(絕對值) dsiw2: mov bx,10 push bx ;10壓入堆棧,作為退出標志 dsiw3: cmp ax,0 ;數據(商)為零,轉向顯示 jz dsiw4 xor dx,dx ;擴展被除數DX.AX div bx ;數據除以10:DX.AX÷10 add dl,30h ;余數(0~9)轉換為ASCII碼 push dx ;數據各位先低位后高位壓入堆棧 jmp dsiw3 dsiw4: pop dx ;數據各位先高位后低位彈出堆棧 cmp dl,10 ;是結束標志10,則退出 je dsiw5 mov ah,2 ;進行顯示 int 21h jmp dsiw4 dsiw5: pop dx pop bx pop ax ret ;子程序返回 dispsiw endp end
- 計算有符號數的平均值程序
三、宏結構
- 宏匯編
- 定義和調用
-
;聲明
宏名 MACRO[形參表] …… …… ENDM
;調用
宏名 實參列表 - 例:
- 宏定義
WriteString macro msg push ax push dx lea dx,msg mov ah,9 int 21h pop dx pop ax endm
- 宏調用
WriteString msg
- 宏展開
push ax push dx lea dx,msg mov ah,9 int 21h pop dx pop ax
- 宏定義
-
- 宏和子程序
- 宏:
- 僅是源程序級的簡化:宏調用在匯編時進行程序語句的展開,不需要返回;不減小目標程序,執行速度沒有改變
-
通過形參、實參結合實現參數傳遞,簡捷直觀、靈活多變
-
當程序段較短或要求較快執行時,應選用宏
-
子程序
-
還是目標程序級的簡化:子程序調用在執行時由CALL指令轉向、RET指令返回;形成的目標代碼較短,執行速度減慢
-
需要利用寄存器、存儲單元或堆棧等傳遞參數
-
當程序段較長或為減小目標代碼時,要選用子程序
-
- 宏:
- 定義和調用
-
一些其他的說明
-
MASM具體支持的多模塊程序結構的方法:
-
源文件包含
-
模塊連接
-
子程序庫
-
庫文件包含
-
-
源文件包含
-
各種常量定義、聲明語句等組織在包含文件(*.inc)
-
常用的或有價值的宏定義放在宏定義文件(*.mac)
-
常用的子程序形成匯編語言源文件(*.asm)
-
-
使用源文件包含偽指令INCLUDE將指定的文本文件內容插入主題源程序文件。
-
四、課后習題!
5.1(1)指令“CALL BX“采用了指令的什么尋址方式?
寄存器間接尋址
(5)子程序采用堆棧傳遞參數,為什么要特別注意堆棧平衡問題?
子程序保持堆棧平衡才能保證執行RET指令時當前棧頂的內容是正確的返回地址。
主程序保持平衡才能釋放傳遞參數占用的堆棧空間,否則多次調用可能使堆棧溢出。
5.2 判斷題
(2)CALL指令的執行並不影響堆棧指針SP。
錯,IP入棧 sp-2
(5)子程序需要保護寄存器,包括保護傳遞入口參數和出口參數的通用寄存器。
錯,出口參數寄存器不能保護。
(6)利用INCLUDE包含的源文件實際上只是源程序的一部分。
對。
(7)宏調用與子程序調用一樣都要使用CALL指令實現。
錯。宏調用:宏名+參數列表
(8)宏定義可以與子程序一樣,書寫與主程序之后。
錯,不可以。
5.3 填空題
(1)指令”RET i16"的功能相當於"RET"指令和“ADD SP, 2 .”組合。
(4)數值10在計算機內部用二進制“1010”編碼表示,用十六進制表達是: 0AF .如果將該編碼加37H,則為 41H ,他是字符 A 的ASCII碼值。
(5)利用堆棧傳遞子程序參數的方法是固定的,例如尋址堆棧段數據的寄存器是 BP 。
(7)過程定義開始是“TEST PROC"語句,則過程定義結束的語句是 TEST ENDP 。宏定義開始是”DISP MACRO"語句,則宏定義結束的語句是 ENDM 。
5.5 請按如下說明編寫子程序。
子程序功能:把用ASCII碼表示的兩位十進制數轉換為壓縮BCD碼
入口參數:DH=十位數的ASCII碼,DL=個位數的ASCII碼
出口參數:AL=對應BCD碼
;把用asc碼表示的兩位十進制數轉換稱壓縮的BCD碼 ;入口參數:DH=十位數的ASC碼,DL=個位數的ASC碼 ;出口參數:AL=對應的BCD碼 .model small .stack .data ;數據段 inmsg db 'Enter two numbers(0-9): $' errmsg db 0dh,0ah,'Input error, enter again: $' .code ;代碼段,主程序 .startup mov dx,offset inmsg mov ah,9 int 21h ;輸入兩位十進制數字,結果分別保存到DH和DL rdhw1: xor dx,dx ;;DX一開始是0哇 mov bx,2 ;限制輸入字符的個數 mov cl,8 rdhw2: mov ah,1 int 21h ;輸入一個字符;;輸入進AL cmp al,'0' ;檢測鍵入字符是否合法 jb rderr ;不合法則返回重新輸入 cmp al,'9' ja rderr ;不合法則返回重新輸入 shl dx,cl;;將十位上的數左移至高八位DH上 在第二次循環的時候起作用!! or dl,al;;這步應該是相當於ADD DL,AL吧 dec bx jnz rdhw2 ;繼續輸入 call btobcd ;調用子程序 int 3 ;中斷 jmp done rderr: mov dl,0dh ;輸出回車字符 mov ah,2 int 21h mov dl,0ah ;輸出換行字符 mov ah,2 int 21h lea dx,errmsg ;顯示錯誤信息 mov ah,9 int 21h jmp rdhw1 ;重新輸入 done: nop .exit ;主程序結束,退出 btobcd proc ;子程序 ;出口參數:AL=輸入的數據 push cx xor ax,ax mov cl,4 and dh,0fh ;dh高4位清零 or al,dh ;AL和DH相加;;AL不是零么??? and dl,0fh ;減30H;;為啥不直接用SUB呢???為什么??? shl ax,cl ;AX左移4位 or al,dl ;AL和DL相加 pop cx ret btobcd endp end ;為什么子程序保護了CX不保護DX?? 入口參數不用保護嘛??
5.7 編寫一個程序,在鍵盤上按一個鍵,將其返回的ASCII碼值表示出來,如果按下ESC鍵(1BH)則退出。
;顯示輸入字符的ASC碼 ;如果輸入的是ESC(1BH)則退出 .model small .stack .data asc db '00H',13,10,'$' crlf db 13,10,'$' .code .startup ReadAsc: mov ah,1 int 21h cmp al,1Bh jz done ;輸入一個字符,輸入ESC退出;ESC的ASCii碼為1BH mov dx,offset crlf mov ah,9 int 21h ;輸出回車換行 mov cx,2 xor bx,bx;;BX用做asc數組下標 again: rol al,1 ;高4位循環移位進入低4位,作為子程序的入口參數 rol al,1 rol al,1 rol al,1 push ax ;子程序利用AL返回結果,所以需要保存AX中的數據 call htoasc ;調用子程序 mov asc[bx],al ;保存轉換后的ASCII碼 pop ax ;恢復保存的數據 inc bx loop again mov dx,offset asc mov ah,9 int 21h ;顯示 jmp ReadAsc done: nop .exit htoasc proc ;將AL低4位表達的一位十六進制數轉換為ASCII碼 and al,0fh ;只取AL的低4位 or al,30h ;AL高4位變成3,實現加30H cmp al,39h ;是0~9,還是A~F jbe htoend add al,7 ;是A~F,其ASCII碼再加上7 htoend: ret ;子程序返回 htoasc endp end
5.8 編寫一個子程序,它以二進制形式顯示AL中8位數據,並設計一個主程序驗證。
;AL中八位數據用二進制顯示 .model small .stack .data .code .startup mov al,35H ;用於驗證結果,子程序運行正確的話應該輸出00110101 call asctob nop .exit asctob proc ;將AL用二進制形式輸出 push ax push bx push cx push dx mov cx,8 mov bl,al ;將al的初始數據賦值給bl again: xor dl,dl rol bl,1 adc dl,30h mov ah,2 int 21h loop again pop dx pop cx pop bx pop ax ret ;子程序返回 asctob endp end
.model small .stack .data .code .startup mov al,12h mov cx,8 mov bl,al again: call cout loop again .exit cout proc push ax push dx shl bl,1 jc one mov dl,'0' jmp over one: mov dl,'1' over:mov ah,2 int 21h pop dx pop ax ret cout endp end
5.11 編寫一個計算字節校驗和的子程序。所謂“校驗和”是指不計進位的累加,常用於檢查信息的正確性。主程序提供入口參數,有數據個數和數據緩沖區的首地址。子程序回送求和結果和這個出口參數。
;eg101.asm .model small .stack .data array db 1,2,3,4,5,6 sum db ? .code .startup mov bx,offset array ;入口參數1:數據緩沖區首地址 mov cx,lengthof array ;入口參數2:數據個數 call getSum int 3 mov sum, al ;出口參數為al .exit getSum proc ;求校驗和 push bx push cx xor ax,ax again: add al,[bx] inc bx loop again pop cx pop bx ret ;子程序返回 getSum endp end
5.13 設計一個從低地址到高地址逐個字節顯示某個主存區與內容的子程序DISPMEM。入口參數:AX=主存偏移地址,CX=字節個數(主存區域的長度)。同時編寫一個主程序進行驗證。
;將ax=主存偏移地址,cx=字節個數的主存區域從低地址到高地址逐個字節顯示 .model small .stack .data MSG db 'abcdefg' crlf db 13,10,'$' .code .startup MOV AX,OFFSET MSG MOV CX,20 CALL DISP .exit DISP PROC PUSH AX PUSH SI PUSH DX PUSH BX XOR BX,BX MOV BX,AX XOR SI,SI A1: MOV AL,[BX+SI] ROL AL,1 ROL AL,1 ROL AL,1 ROL AL,1 PUSH AX AND AL,0FH OR AL,30H CMP AL,39H JBE A2 ADD AL,7 A2: MOV DL,AL MOV AH,2 INT 21H INC SI POP AX ROL AL,1 ROL AL,1 ROL AL,1 ROL AL,1 AND AL,0FH OR AL,30H CMP AL,39H JBE A3 ADD AL,7 A3: MOV DL,AL MOV AH,2 INT 21H MOV DX,OFFSET crlf MOV AH,9 INT 21H LOOP A1 POP BX POP DX POP SI POP AX RET DISP ENDP end
