|
一、子程序结构
- 子程序=函数=过程
- 子程序指令
- 子程序调用指令
-
CALL LABEL ;调用标号、子程序名指定的子程序
- 分成近调用(段内调用)和远调用(段间调用)
- 入栈返回地址:将CALL下条指令的地址压入堆栈
- 子程序返回指令
- 过程定义伪指令
- 子程序设计
- 利用过程定义伪指令声明
- 最后利用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
- 宏调用
- 宏展开
push ax
push dx
lea dx,msg
mov ah,9
int 21h
pop dx
pop ax
- 宏和子程序
-
一些其他的说明
四、课后习题!
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
来源:https://www.cnblogs.com/hotaru-klxx/p/15524216.html |