汇编语言 程序设计
第 6章 子程序设计及系统调 用
◆ 调用程序与子程序
◆ 调用与返回指令
◆ 子程序设计
汇编语言 程序设计
6.1 调用程序与子程序
子程序,在许多应用程序中, 常常需要多次使用某功能的
指令序列 。 这时, 为了减少重复编写程序, 节省内存空间,
把这一功能的指令序列组成一个相对独立的程序段 。 在程序
运行时, 如果需要使用这个给定的功能, 就转移到这个独立
的程序段, 待这个独立的程序段指令序列执行完后, 又返回
到原来位置继续运行程序 。 我们把这个相对独立的程序段就
叫子程序或过程 。
调用程序,编制程序时, 按需要转向子程序, 称为子程序
调用, 或称为过程调用 。 调用子程序的程序称为调用程序或
主程序 。 主, 子程序是相对而言的 。 但子程序一定是受调用
程序或主程序调用的 。
返回
汇编语言 程序设计
6.2 调用与返回指令
1.过程调用指令 CALL
? 指令格式,CALL DST
其中 DST为过程的目标地址 。
? 指令功能,把 CALL指令的下一条指令地址 ( 称为返回点或断点 )
推入堆栈保存, 然后转到目标地址 ( DST) 。
? CALL指令可以在段内, 段间调用, 寻址方式分为直接和间接两种 。
( 1) 段内直接调用
? 指令中 DST给出转向地址 。 首先将指令指针 IP推入堆栈保存, 然后把
从指令中得到的距目标过程相对偏移量 ( 最大为 32K字节 ) 加到指令
指针 IP上 ( 得到子程序的入口地址 ), 实现过程调用 。
? 执行的操作为,( SP) ← ( SP) - 2
(( SP) +1,( SP)) ← ( IP)
( IP) ← ( IP) +D16
其中 D16为机器指令的位移量, 它是转向地址和返回地址之间的差直 。
汇编语言 程序设计
( 2) 段内间接调用
执行的操作为,( SP) ← ( SP) - 2
(( SP) +1,( SP)) ← ( IP)
( IP) ← ( EA)
其中 EA 是由 DST的寻址方式所确定的有效地址。
( 3) 段间直接调用
首先把现行的代码段寄存器 CS的内容和指令指针 IP的值入栈保存,
然后把指令中的地址偏移字和段地址字送入 IP和 CS。
执行的操作为,( SP) ← ( SP) - 2
(( SP) +1,( SP)) ← ( CS)
( SP) ← ( SP) - 2
(( SP) +1,( SP)) ← ( IP)
( IP) ← 偏移地址 ( 指令的第 2,3个字节 )
( CS) ← 段地址 ( 指令的第 4,5个字节 )
汇编语言 程序设计
( 4) 段间间接调用
首先把现行的代码段寄存器 CS的内容和指令指针 IP的值入栈保存,
然后把指令中的地址偏移字和段地址字送入 IP和 CS。
执行的操作为,( SP) ← ( SP) - 2
(( SP) +1,( SP)) ← ( CS)
( SP) ← ( SP) - 2
(( SP) +1,( SP)) ← ( IP)
( IP) ← ( EA)
( CS) ← ( EA+2)
其中 EA 是由 DST的寻址方式所确定的有效地址 。
2,返回指令 RET
? 指令格式,RET
? 指令功能,RET指令通常写在一个子程序 ( 或过程 ) 的最后, 用以返
回到调用这个子程序的断点处 。
汇编语言 程序设计
RET指令也属于无条件转移指令 。 可以在段内或段间返回 。
( 1) 段内返回
RET指令由堆栈弹回断点偏移量到指令指针 IP,实现段内调用返回 。
执行的操作为:
( IP) ← (( SP) +1,( SP))
( SP) ← ( SP) +2
( 2) 段间返回
RET指令除由堆栈弹回断点偏移量到指令指针 IP外, 还由堆栈弹回断
点所在段基址到代码段寄存器 CS,实现段间调用返回 。
执行的操作为,( IP) ← (( SP) +1,( SP))
( SP) ← ( SP) +2
( CS) ← (( SP) +1,( SP))
( SP) ← ( SP) +2
返回
汇编语言 程序设计
6.3 子程序设计
6.3.1 子程序定义
6.3.2 子程序的调用与返回
6.3.3 现场保护与恢复
6.3.4 参数的传递方式
6.3.5 子程序调用举例
6.3.6 子程序的嵌套与递归
返回
汇编语言 程序设计
6.3.1 子程序定义
格式:
过程名 PROC [NEAR/FAR]
过程名 ENDP
其中 PROC表示过程定义开始, ENDP表示过程定义结束 。
一般过程名同标号一样, 具有三种属性, 即段属性, 偏移地址属性以及
类型属性 。 而类型属性可指定为 NEAR或 FAR两种类型 。 具有 NEAR属性的子
程序与调用程序应在同一个逻辑段中, 而具有 FAR属性的子程序和调用程
序不在同一个逻辑段内 。 若为 NEAR类型属性时可以省略 ? NEAR”。
如下面的定义皆为正确的过程定义:
段内调用:
A PROC NEAR A PROC
或
A ENDP A ENDP
汇编语言 程序设计
段间调用:
B PROC FAR
B ENDP
6.3.2 子程序的调用与返回
1,段内调用
前面已经讲过, 子程序调用可以在段内调用, 也可以在段间调用 。 如果
是段内调用, 则在过程定义时, 必须定义为 NEAR类型 。 这时, 过程定
义可放在代码段中, 置于主程序体之前或之后 。
【 例 6.1】 已知三个八位无符号数 X,Y,Z,分别存放于 BUF,BUF+1和
BUF+2存储单元, 计算 2X+5Y+8Z,结果送 RES和 RES+1单元 。
返回
汇编语言 程序设计
NAME EXAM6_1
DATA SEGMENT
BUF DB 71H,0A4H,9BH
RES DB 2 DUP(?)
DATA ENDS
STACK SEGMENT PARA STACK 'STACK'
STAPN DB 100 DUP(?)
TOP EQU LENGTH STAPN
STACK ENDS
CODE SEGMENT
ASSUME CS:CODE,DS:DATA,SS:STACK
START:MOV AX,DATA
MOV DS,AX
MOV AX,STACK
MOV SS,AX
MOV AX,TOP
MOV SP,AX
MOV AX,0 ; AX清 0
MOV WORD PTR RES,AX ; RES字单元清 0
汇编语言 程序设计
LEA BX,BUF ; 置地址指针
MOV AL,2
CALL MULL ; 过程调用
MOV AL,5
CALL MULL ; 过程调用
MOV AL,8
CALL MULL ; 过程调用
MOV AH,4CH
INT 21H
MULL PROC ; 乘法子程序
MUL BYTE PTR [BX] ; 做乘法结果在 AX
ADD WORD PTR RES,AX ; 做加法
MOV AX,0 ; AX清 0
INC BX ; 地址加 1
RET ; 返回主程序
MULL ENDP
CODE ENDS
END START
汇编语言 程序设计
2,段间调用
子程序如果段间调用时, 必须定义为 FAR类型 。
段间调用通常用于不同模块之间的调用 。
编写不同模块的段间调用程序, 应该注意以下几个个问题:
( 1) 主程序模块和子程序模块分别汇编, 然后用连接程序
将它们连接在一起 。
( 2) 在主程序模块中, 主程序所调用的外部过程名必须用
EXTRN伪指令说明 。
( 3) 在过程模块中, 提供给外段调用的过程名必须用
PUBLIC伪指令说明 。
( 4) 模块间其它公用符号名及外部符号名的定义不可缺少 。
【 例 6.2】 将 【 例 6.1】 中的段内调用改为段间调用
汇编语言 程序设计
源程序为:
NAME EXAM6_2
EXTRN MULL,FAR ; 外部引用说明
PUBLIC RES ; 定义公用名
DATA SEGMENT
BUF DB 71H,0A4H,9BH
RES DB 2 DUP(?)
DATA ENDS
STACK SEGMENT PARA STACK 'STACK'
STAPN DB 100 DUP(?)
TOP EQU LENGTH STAPN
STACK ENDS
CODE SEGMENT
ASSUME CS:CODE,DS:DATA,SS:STACK
START:MOV AX,DATA
MOV DS,AX
MOV AX,STACK
MOV SS,AX
MOV AX,TOP
MOV SP,AX
MOV AX,0 ; AX清 0
MOV WORD PTR RES,AX ; RES字单元清 0
LEA BX,BUF ; 置地址指针
汇编语言 程序设计
MOV AL,2
CALL MULL ; 过程调用
MOV AL,5
CALL MULL ; 过程调用
MOV AL,8
CALL MULL ; 过程调用
MOV AH,4CH
INT 21H
CODE ENDS
END START
NAME L6_2A
EXTRN RES,BYTE ; 外部引用说明
CODEA SEGMENT
MULL PROC FAR ; 乘法子程序
ASSUME CS,CODEA
PUBLIN MULL ; 定义公用名
MUL BYTE PTR [BX] ; 做乘法结果在 AX
ADD WORD PTR RES,AX ; 做加法
MOV AX,0 ; AX清 0
INC BX ; 地址加 1
RET ; 返回主程序
MULL ENDP
CODEA ENDS
END 返回
汇编语言 程序设计
6.3.3 现场保护与恢复
? 要保护的寄存器:应该是在子程序中将被使用, 返回调用程序后仍然需要使
用其原有内容的那些寄存器 。 即保护调用程序和子程序两者在使用上发生冲
突的那些寄存器 。 但在编程时, 一时很难弄清哪些是有冲突的寄存器, 一种
较为简单的方法是把所有的寄存器均加以保护 。
? 一般在子程序中进行寄存器保护较好 。 即在子程序的开始部分, 先进行相关
寄存器 ( 主要是在子程序中使用的各寄存器 ) 的保护 。 然后再进行子程序的
处理操作 。 在执行完子程序后, 返回前, 先恢复各寄存器内容后, 再返回调
用程序 。 例如:
SUBT PROC NEAR
PUSH AX
PUSH BX
PUSH CX
PUSH DX
POP DX
POP CX
POP BX
POP AX
RET
SUBT ENDP 返回
汇编语言 程序设计
6.3.4 参数的传递方式
参数传递通常有三种方法:寄存器、存储器和堆栈分别作为传递的工具 。
1.利用寄存器传递参数
实现的方法是把子程序所需要的入口参数, 由调用程序予先放入指
定的寄存器中 。 在进入子程序后, 子程序就可直接对这些寄存器内容
进行操作了 。 同样子程序的运行结果, 也可置入寄存器中, 把它们作
为子程序的出口参数寄存器使用 。
【例 6.3】以 BCDBUF为首址的内存缓冲区存放着若干单元的用 BCD
码表示的十进制数。每个单元中放两位 BCD码,要求把它们分别转换
为 ASCII码,存放在 ASCBUF为首址的缓冲区中,且高 4位 BCD码转换成
的 ASCII码放在地址较高的单元。并且要求边转换边显示这些 ASCII码。
源程序为:
NAME EXAM6_3
DATA SEGMENT
BCDUBF DB 71H,24H,96H,87H,12H,78H,56H,34H,63H,45H
COUNT EQU $-BCDBUF
ASCBUF DB 20 DUP(?)
DATA ENDS
汇编语言 程序设计
STACK SEGMENT PARA STACK’ STACK’
STAPN DB 100 DUP(?)
TOP EQU LENGTH STAPN
STACK ENDS
CODE SEGMENT
ASSUME CS:CODE,DS:DATA,ES:DATA,SS:STACK
START,MOV AX,DATA
MOV DS,AX
MOV ES,AX
MOV AX,STACK
MOV SS,AX
MOV AX,TOP
MOV SP,AX
MOV SI,OFFSET BCDBUF ; BCD码首址
MOV DI,OFFSET ASCBUF ; ASCII码首址
MOV CX,COUNT ; 组合 BCD码个数
CLD ; DF=0
LD,LODSB ; 取一个组合 BCD码
MOV BL,AL ; 保存
AND AL,0FH
OR AL,30H ; BCD码低位转换为 ASCII码
汇编语言 程序设计
MOV DL,AL ; 存入 DL
STOSB ; 存入 ASCII码存储区
CALL DISP ; 显示 ASCII码字符
MOV AL,BL ; BCD码送回 AL
PUSH CX ; 保存计数
MOV CL,4
SHR AL,CL
OR AL,30H ; BCD码高位转换为 ASCII码
MOV DL,AL ; 存入 DL
STOSB ; 存入 ASCII码存储区
CALL DISP ; 显示 ASCII码
POP CX ; 弹出计数
LOOP LP ; 计数减 1不为 0继续
MOV AH,4CH
INT 21H; 子程序名,DISP; 功能:显示 ASCII字符;入口参数,ASCII码在 DL中
汇编语言 程序设计
DISP PROC
MOV AH,2 ; 2号系统功能调用
INT 21H
MOV DL,‘ ’
MOV AH,2
INT 21H
RET
DISP ENDP
CODE ENDS
END START
2,利用存储器传递参数
利用存储器参数传递, 适合于参数较多的情况 。 大多是在数据区建立参数
表, 里面放有子程序所要使用的参数 。 调用程序把该参数表首地址传送给子
程序 。 子程序通过参数表取得所需参数, 在数据处理完后, 将结果也送到指
定的数据储存区中 。
【 例 6.4】 将例 【 例 6.3】 的程序改为用存储器传递参数 。
汇编语言 程序设计
源程序为:
NAME EXAM6_4
DATA SEGMENT
BCDUBF DB 71H,24H,96H,87H,12H,78H,56H,34H,63H,45H
COUNT EQU $-BCDBUF
ASCBUF DB 20 DUP(?)
DATA ENDS
STACK SEGMENT PARA STACK’STACK’
STAPN DB 100 DUP(?)
TOP EQU LENGTH STAPN
STACK ENDS
CODE SEGMENT
ASSUME CS:CODE,DS:DATA,ES:DATA,SS:STACK
START,MOV AX,DATA
MOV DS,AX
MOV ES,AX
MOV AX,STACK
MOV SS,AX
MOV AX,TOP
MOV SP,AX
MOV SI,OFFSET BCDBUF ; BCD码首址
汇编语言 程序设计
MOV DI,OFFSET ASCBUF ; ASCII码首址
MOV CX,COUNT ; 组合 BCD码个数
CLD ; DF=0
LP,LODSB
MOV BL,AL
AND AL,OFH
OR AL,30H
STOSB
CALL DISP
MOV AL,BL
PUSH CX
MOV CL,4
SHR AL,CL
OR AL,30H
STOSB
CALL DISP
POP CX
LOOP LP
MOV AH,4CH
INT 21H
汇编语言 程序设计;子程序名,DISP; 功能:显示 ASCII字符;入口参数,DI指向 ASCII码单元
DISP PROC
PUSH DI
DEC DI ; 该 DI是要显示字符所在单元地址
MOV DL,[DI]
MOV AH,2
INT 21H
POP DI
MOV DL,‘ ’
MOV AH,2
INT 21H
RET
DISP ENDP
CODE ENDS
END START
汇编语言 程序设计
3,利用堆栈传递参数
利用堆栈进行参数传递, 就是在主程序中将参数推入堆栈, 而在子
程序中将参数从堆栈中弹出 。
用堆栈传递参数也适于多参数的情况, 但要注意堆栈后进先出的特点,
避免参数进出栈的混乱 。
【 例 6.5】 将例 【 例 6.3】 的程序改为用堆栈传递参数 。
LP,LODSB
MOV BL,AL
AND AL,OFH
OR AL,30H
MOV AH,0
PUSH AX ; 保存低位 BCD码对应的 ASCII码
STOSB
CALL DISP
MOV AL,BL
MOV DX,CX
汇编语言 程序设计
MOV CL,4
SHR AL,CL
OR AL,30H
MOV CX,DX
MOV AH,0
PUSH AX
STOSB
CALL DISP
LOOP LP
MOV AH,4CH
INT 21H; 子程序名,DISP; 功能:显示 ASCII字符;入口参数,ASCII在堆栈中
DISP PROC
MOV BP,SP
MOV DL,[BP+2] ; 取出 ASCII码字符送入 DL
MOV AH,2
INT 21H
MOV DL,‘ ’
汇编语言 程序设计
MOV AH,2
INT 21H
RET
DISP ENDP
CODE ENDS
END START
请大家注意:前面所介绍的三种参数的传递方法, 并不是固定不变
的, 即它们是可以综合使用的 。 依实现的需要和情况的不同, 可以使
用其中一种方式, 也可以同时使用几种方式的混合 。 有的时候还可能
并不需要参数传递 。
返回
汇编语言 程序设计
6.3.5 子程序调用举例
【 例 6.6】 将一个给定的二进制数按位转换成相应的 ASCII码字符串, 送
到指定的存储单元显示 。 如二进制数 10010011转换成字符串为
‘ 10010011’ 。 要求将转换过程写成子程序, 且子程序应具有较好的
通用性, 而必须能实现对 8位和 16位二进制数的转换 。
入口参数,DX存放待转换的二进制数
CX存放待转换数的位数 ( 8位或 16位 )
DX存放 ASCII码首址
出口参数:转换后字符串放在以 DI作指针的字节存储区中 。
NAME EXAM6_6
DATA SEGMENT
NUM8 DB 93H
NUM16 DW 0ABCDH
ASCBUF DB 20 DUP(0)
DATA ENDS
STACK SEGMENT STACK
DB 200 DUP(0)
STACK ENDS
CODE SEGMENT
汇编语言 程序设计
ASSUME DS:DATA,CS:CODE,SS:STACK
START,MOV AX,DATA
MOV DS,AX
MOV DX,0
MOV DL,NUM8 ; 转换二进制数送 DX
MOV CX,8
LEA DI,ASCBUF ; 字符串首址 → DI
CALL BTASC ; 调用子程序 BTASC
MOV [DI],BYTE PTR 0DH
MOV [DI+1],BYTE PTR 0AH
MOV [DI+2],BYTE PTR '$'
LEA DX,ASCBUF
MOV AH,9
INT 21H
MOV DX,NUM16
MOV CX,16 ; 置位数 16
LEA DI,ASCBUF
CALL BTASC
汇编语言 程序设计
MOV [DI],BYTE PTR 0DH
MOV [DI+1],BYTE PTR 0AH
MOV [DI+2],BYTE PTR '$' ; 显示转换后的字符串
LEA DX,ASCBUF
MOV AH,9
INT 21H
MOV AH,4CH
INT 21H
BTASC PROC
PUSH AX ; 保存 AX
MOV AL,0
CMP CX,8 ; 比较 8位数
JNE L1 ; 直接转换 16位数
MOV DH,DL ; 8位数转换送 DH
L1,ROL DX,1 ; DX最高位移入 CF
MOV AL,0
RCL AL,1 ; CF移入 AL最低位
汇编语言 程序设计
ADD AL,30H
MOV BYTE PTR [DI],AL
INC DI
LOOP L1
POP AX
RET
BTASC ENDP
CODE ENDS
END START
【 例 6.7】 已知某班 N个学生的成绩, 试编制一个子程序统计不及格, 60~ 69分,
70~ 79分, 80~ 89分, 90~ 99分及 100分的人数, 分别存放到以 S为首址的单
元中 。
入口参数:以变量 SCORE为首址的字存储单元的值 。
CX存储待处理的学生人 。
出口参数:以 S为首址的字存储区的值 。
汇编语言 程序设计
NAME EXAM6_7
STACK SEGMENT STACK
DB 200 DUP (0)
STACK ENDS
DATA SEGMENT
SCORE DW 78,89,83,54,35,76,74,85,90,100,66,95
N EQU ($-SCORE)/2
S DW 6 DUP (0)
CODE SEGMENT
ASSUME CS:CODE,DS:DATA,SS:STACK
START,MOV AX,DATA
MOV DS,AX
MOV CX,N
CALL COUNT
MOV AH,4CH
INT 21H
COUNT PROC
MOV SI,0
NEXT,MOV AX,SCORE[SI]
CMP AX,60
JB L1
MOV BX,10
DIV BL
汇编语言 程序设计
CBW
MOV BX,AX
SUB BX,5
SAL BX,1
INC WORD PTR S[BX]
JMP L2
L1,MOV BX,0
SAL BX,1
INC WORD PTR S[BX]
L2,ADD SI,2
LOOP NEXT
RET
COUNT ENDP
CODE ENDS
END START
【 例 6.8】 将 AX中的十六位有符号二进制数以十进制形式显示输出子程序 F2T10。
该程序首先判断 AX中数的符号, 若数为负数, 则将负号, -” 送入输出缓冲区, 并求
( AX) 的绝对值;若 AX中的数为正数, 则不做其他处理, 此时 ( AX) 即为无符号二
进制数 。 然后将无符号二进制数转换成十进制数, 可采用将 ( AX) 除以 10,得到第
一个商和第一个余数, 第一个余数就是所求十进制数的个数;将第一个商除以 10,
得到第二个商和余数, 第二余数就是所求十进制数的十位数 ……,这一过程一直循环
到商数为 0时,
汇编语言 程序设计
得到的余数就是所求十进制数的最高位数, 为了得到转换后的十进制数 ASCII字
符串, 可利用堆栈的先进后出原则来实现 。
NAME EXAM6_8
DATA SEGMENT
BINARY DW 7FFFH,0,0FFFEH,50H,8000H,1000H,2000H
N=($-BINARY)/2
DATA ENDS
STACK SEGMENT STACK
DB 200 DUP (0)
STACK ENDS
CODE SEGMENT
ASSUME CS:CODE,DS:DATA,SS:STACK
START,MOV AX,DATA
MOV DS,AX
MOV CX,N
LEA DI,BINARY
LOPA,MOV AX,[DI]
CALL F2T10
MOV DL,','
MOV AH,2
汇编语言 程序设计
INT 21H
ADD DI,2
LOOP LOPA
MOV AH,4CH
INT 21H
DATA SEGMENT
BUF DB 7 DUP (?)
DATA ENDS
F2T10 PROC
PUSH BX
PUSH DX
PUSH SI
PUSH CX
LEA SI,BUF
OR AX,AX
JNS PLUS
NEG AX
MOV [SI],BYTE PTR '-'
INC SI
PLUS,MOV BX,10
MOV CX,0
LOP1,MOV DX,0
DIV BX
PUSH DX
汇编语言 程序设计
INC CX
OR AX,AX
JNE LOP1
LOP2,POP AX
CMP AL,10
JB L1
ADD AL,7
L1,ADD AL,30H
MOV [SI],AL
INC SI
DEC CX
JNE LOP2
MOV [SI],BYTE PTR '$'
LEA DX,BUF
MOV AH,9
INT 21H
POP CX
POP SI
POP DX
POP BX
RET
F2T10 ENDP
CODE ENDS
END START
返回
汇编语言 程序设计
6.3.6 子程序的嵌套与递归
1,子程序的嵌套
一个程序可以调用一个或多个子程序 。 那么一个子程序是否也可以调用
另一个子程序呢? 回答是肯定的 。 这种一个子程序调用另一个子程序
的情况, 称为子程序的嵌套 。
主程序
MA
子程序 S1 子程序 S2
CALL S1
END
S1 PROC
CALL S2
RET
S2 PROC
RET
图 6.1 子程序嵌套示意图
汇编语言 程序设计
2,子程序的递归调用
子程序的递归调用是指一个子程序直接或间接地调用自己 。 递归子程序一
般对应于数学上对函数的递归定义, 它往往能设计出效率较高的程序, 完成
相当复杂的计算, 递归调用要注意必须有结束递归调用的判断语句 。 这里以
阶层函数为例, 说明递归子程序的设计方法 。
STACK SEGMENT STACK
DB 200 DUP(0)
STACK ENDS
DATA SEGMENT
N DW 5
RESULT DW?
DATA ENDS
CODE SEGMENT
ASSUME CS:CODE,SS:STACK,DS:DATA
START,MOV AX,DATA
MOV DS,AX
MOV AX,N
CALL FACT
MOV AX,RESULT
MOV AH,4CH
汇编语言 程序设计
INT 21H
FACT PROC
CMP AX,0
JNE L1
MOV RESULT,1
JMP EXIT
L1,PUSH AX
DEC AX
CALL FACT
POP AX
MUL RESULT
MOV RESULT,AX
EXIT,RET
FACT ENDP
CODE ENDS
END START 返回
第 6章 子程序设计及系统调 用
◆ 调用程序与子程序
◆ 调用与返回指令
◆ 子程序设计
汇编语言 程序设计
6.1 调用程序与子程序
子程序,在许多应用程序中, 常常需要多次使用某功能的
指令序列 。 这时, 为了减少重复编写程序, 节省内存空间,
把这一功能的指令序列组成一个相对独立的程序段 。 在程序
运行时, 如果需要使用这个给定的功能, 就转移到这个独立
的程序段, 待这个独立的程序段指令序列执行完后, 又返回
到原来位置继续运行程序 。 我们把这个相对独立的程序段就
叫子程序或过程 。
调用程序,编制程序时, 按需要转向子程序, 称为子程序
调用, 或称为过程调用 。 调用子程序的程序称为调用程序或
主程序 。 主, 子程序是相对而言的 。 但子程序一定是受调用
程序或主程序调用的 。
返回
汇编语言 程序设计
6.2 调用与返回指令
1.过程调用指令 CALL
? 指令格式,CALL DST
其中 DST为过程的目标地址 。
? 指令功能,把 CALL指令的下一条指令地址 ( 称为返回点或断点 )
推入堆栈保存, 然后转到目标地址 ( DST) 。
? CALL指令可以在段内, 段间调用, 寻址方式分为直接和间接两种 。
( 1) 段内直接调用
? 指令中 DST给出转向地址 。 首先将指令指针 IP推入堆栈保存, 然后把
从指令中得到的距目标过程相对偏移量 ( 最大为 32K字节 ) 加到指令
指针 IP上 ( 得到子程序的入口地址 ), 实现过程调用 。
? 执行的操作为,( SP) ← ( SP) - 2
(( SP) +1,( SP)) ← ( IP)
( IP) ← ( IP) +D16
其中 D16为机器指令的位移量, 它是转向地址和返回地址之间的差直 。
汇编语言 程序设计
( 2) 段内间接调用
执行的操作为,( SP) ← ( SP) - 2
(( SP) +1,( SP)) ← ( IP)
( IP) ← ( EA)
其中 EA 是由 DST的寻址方式所确定的有效地址。
( 3) 段间直接调用
首先把现行的代码段寄存器 CS的内容和指令指针 IP的值入栈保存,
然后把指令中的地址偏移字和段地址字送入 IP和 CS。
执行的操作为,( SP) ← ( SP) - 2
(( SP) +1,( SP)) ← ( CS)
( SP) ← ( SP) - 2
(( SP) +1,( SP)) ← ( IP)
( IP) ← 偏移地址 ( 指令的第 2,3个字节 )
( CS) ← 段地址 ( 指令的第 4,5个字节 )
汇编语言 程序设计
( 4) 段间间接调用
首先把现行的代码段寄存器 CS的内容和指令指针 IP的值入栈保存,
然后把指令中的地址偏移字和段地址字送入 IP和 CS。
执行的操作为,( SP) ← ( SP) - 2
(( SP) +1,( SP)) ← ( CS)
( SP) ← ( SP) - 2
(( SP) +1,( SP)) ← ( IP)
( IP) ← ( EA)
( CS) ← ( EA+2)
其中 EA 是由 DST的寻址方式所确定的有效地址 。
2,返回指令 RET
? 指令格式,RET
? 指令功能,RET指令通常写在一个子程序 ( 或过程 ) 的最后, 用以返
回到调用这个子程序的断点处 。
汇编语言 程序设计
RET指令也属于无条件转移指令 。 可以在段内或段间返回 。
( 1) 段内返回
RET指令由堆栈弹回断点偏移量到指令指针 IP,实现段内调用返回 。
执行的操作为:
( IP) ← (( SP) +1,( SP))
( SP) ← ( SP) +2
( 2) 段间返回
RET指令除由堆栈弹回断点偏移量到指令指针 IP外, 还由堆栈弹回断
点所在段基址到代码段寄存器 CS,实现段间调用返回 。
执行的操作为,( IP) ← (( SP) +1,( SP))
( SP) ← ( SP) +2
( CS) ← (( SP) +1,( SP))
( SP) ← ( SP) +2
返回
汇编语言 程序设计
6.3 子程序设计
6.3.1 子程序定义
6.3.2 子程序的调用与返回
6.3.3 现场保护与恢复
6.3.4 参数的传递方式
6.3.5 子程序调用举例
6.3.6 子程序的嵌套与递归
返回
汇编语言 程序设计
6.3.1 子程序定义
格式:
过程名 PROC [NEAR/FAR]
过程名 ENDP
其中 PROC表示过程定义开始, ENDP表示过程定义结束 。
一般过程名同标号一样, 具有三种属性, 即段属性, 偏移地址属性以及
类型属性 。 而类型属性可指定为 NEAR或 FAR两种类型 。 具有 NEAR属性的子
程序与调用程序应在同一个逻辑段中, 而具有 FAR属性的子程序和调用程
序不在同一个逻辑段内 。 若为 NEAR类型属性时可以省略 ? NEAR”。
如下面的定义皆为正确的过程定义:
段内调用:
A PROC NEAR A PROC
或
A ENDP A ENDP
汇编语言 程序设计
段间调用:
B PROC FAR
B ENDP
6.3.2 子程序的调用与返回
1,段内调用
前面已经讲过, 子程序调用可以在段内调用, 也可以在段间调用 。 如果
是段内调用, 则在过程定义时, 必须定义为 NEAR类型 。 这时, 过程定
义可放在代码段中, 置于主程序体之前或之后 。
【 例 6.1】 已知三个八位无符号数 X,Y,Z,分别存放于 BUF,BUF+1和
BUF+2存储单元, 计算 2X+5Y+8Z,结果送 RES和 RES+1单元 。
返回
汇编语言 程序设计
NAME EXAM6_1
DATA SEGMENT
BUF DB 71H,0A4H,9BH
RES DB 2 DUP(?)
DATA ENDS
STACK SEGMENT PARA STACK 'STACK'
STAPN DB 100 DUP(?)
TOP EQU LENGTH STAPN
STACK ENDS
CODE SEGMENT
ASSUME CS:CODE,DS:DATA,SS:STACK
START:MOV AX,DATA
MOV DS,AX
MOV AX,STACK
MOV SS,AX
MOV AX,TOP
MOV SP,AX
MOV AX,0 ; AX清 0
MOV WORD PTR RES,AX ; RES字单元清 0
汇编语言 程序设计
LEA BX,BUF ; 置地址指针
MOV AL,2
CALL MULL ; 过程调用
MOV AL,5
CALL MULL ; 过程调用
MOV AL,8
CALL MULL ; 过程调用
MOV AH,4CH
INT 21H
MULL PROC ; 乘法子程序
MUL BYTE PTR [BX] ; 做乘法结果在 AX
ADD WORD PTR RES,AX ; 做加法
MOV AX,0 ; AX清 0
INC BX ; 地址加 1
RET ; 返回主程序
MULL ENDP
CODE ENDS
END START
汇编语言 程序设计
2,段间调用
子程序如果段间调用时, 必须定义为 FAR类型 。
段间调用通常用于不同模块之间的调用 。
编写不同模块的段间调用程序, 应该注意以下几个个问题:
( 1) 主程序模块和子程序模块分别汇编, 然后用连接程序
将它们连接在一起 。
( 2) 在主程序模块中, 主程序所调用的外部过程名必须用
EXTRN伪指令说明 。
( 3) 在过程模块中, 提供给外段调用的过程名必须用
PUBLIC伪指令说明 。
( 4) 模块间其它公用符号名及外部符号名的定义不可缺少 。
【 例 6.2】 将 【 例 6.1】 中的段内调用改为段间调用
汇编语言 程序设计
源程序为:
NAME EXAM6_2
EXTRN MULL,FAR ; 外部引用说明
PUBLIC RES ; 定义公用名
DATA SEGMENT
BUF DB 71H,0A4H,9BH
RES DB 2 DUP(?)
DATA ENDS
STACK SEGMENT PARA STACK 'STACK'
STAPN DB 100 DUP(?)
TOP EQU LENGTH STAPN
STACK ENDS
CODE SEGMENT
ASSUME CS:CODE,DS:DATA,SS:STACK
START:MOV AX,DATA
MOV DS,AX
MOV AX,STACK
MOV SS,AX
MOV AX,TOP
MOV SP,AX
MOV AX,0 ; AX清 0
MOV WORD PTR RES,AX ; RES字单元清 0
LEA BX,BUF ; 置地址指针
汇编语言 程序设计
MOV AL,2
CALL MULL ; 过程调用
MOV AL,5
CALL MULL ; 过程调用
MOV AL,8
CALL MULL ; 过程调用
MOV AH,4CH
INT 21H
CODE ENDS
END START
NAME L6_2A
EXTRN RES,BYTE ; 外部引用说明
CODEA SEGMENT
MULL PROC FAR ; 乘法子程序
ASSUME CS,CODEA
PUBLIN MULL ; 定义公用名
MUL BYTE PTR [BX] ; 做乘法结果在 AX
ADD WORD PTR RES,AX ; 做加法
MOV AX,0 ; AX清 0
INC BX ; 地址加 1
RET ; 返回主程序
MULL ENDP
CODEA ENDS
END 返回
汇编语言 程序设计
6.3.3 现场保护与恢复
? 要保护的寄存器:应该是在子程序中将被使用, 返回调用程序后仍然需要使
用其原有内容的那些寄存器 。 即保护调用程序和子程序两者在使用上发生冲
突的那些寄存器 。 但在编程时, 一时很难弄清哪些是有冲突的寄存器, 一种
较为简单的方法是把所有的寄存器均加以保护 。
? 一般在子程序中进行寄存器保护较好 。 即在子程序的开始部分, 先进行相关
寄存器 ( 主要是在子程序中使用的各寄存器 ) 的保护 。 然后再进行子程序的
处理操作 。 在执行完子程序后, 返回前, 先恢复各寄存器内容后, 再返回调
用程序 。 例如:
SUBT PROC NEAR
PUSH AX
PUSH BX
PUSH CX
PUSH DX
POP DX
POP CX
POP BX
POP AX
RET
SUBT ENDP 返回
汇编语言 程序设计
6.3.4 参数的传递方式
参数传递通常有三种方法:寄存器、存储器和堆栈分别作为传递的工具 。
1.利用寄存器传递参数
实现的方法是把子程序所需要的入口参数, 由调用程序予先放入指
定的寄存器中 。 在进入子程序后, 子程序就可直接对这些寄存器内容
进行操作了 。 同样子程序的运行结果, 也可置入寄存器中, 把它们作
为子程序的出口参数寄存器使用 。
【例 6.3】以 BCDBUF为首址的内存缓冲区存放着若干单元的用 BCD
码表示的十进制数。每个单元中放两位 BCD码,要求把它们分别转换
为 ASCII码,存放在 ASCBUF为首址的缓冲区中,且高 4位 BCD码转换成
的 ASCII码放在地址较高的单元。并且要求边转换边显示这些 ASCII码。
源程序为:
NAME EXAM6_3
DATA SEGMENT
BCDUBF DB 71H,24H,96H,87H,12H,78H,56H,34H,63H,45H
COUNT EQU $-BCDBUF
ASCBUF DB 20 DUP(?)
DATA ENDS
汇编语言 程序设计
STACK SEGMENT PARA STACK’ STACK’
STAPN DB 100 DUP(?)
TOP EQU LENGTH STAPN
STACK ENDS
CODE SEGMENT
ASSUME CS:CODE,DS:DATA,ES:DATA,SS:STACK
START,MOV AX,DATA
MOV DS,AX
MOV ES,AX
MOV AX,STACK
MOV SS,AX
MOV AX,TOP
MOV SP,AX
MOV SI,OFFSET BCDBUF ; BCD码首址
MOV DI,OFFSET ASCBUF ; ASCII码首址
MOV CX,COUNT ; 组合 BCD码个数
CLD ; DF=0
LD,LODSB ; 取一个组合 BCD码
MOV BL,AL ; 保存
AND AL,0FH
OR AL,30H ; BCD码低位转换为 ASCII码
汇编语言 程序设计
MOV DL,AL ; 存入 DL
STOSB ; 存入 ASCII码存储区
CALL DISP ; 显示 ASCII码字符
MOV AL,BL ; BCD码送回 AL
PUSH CX ; 保存计数
MOV CL,4
SHR AL,CL
OR AL,30H ; BCD码高位转换为 ASCII码
MOV DL,AL ; 存入 DL
STOSB ; 存入 ASCII码存储区
CALL DISP ; 显示 ASCII码
POP CX ; 弹出计数
LOOP LP ; 计数减 1不为 0继续
MOV AH,4CH
INT 21H; 子程序名,DISP; 功能:显示 ASCII字符;入口参数,ASCII码在 DL中
汇编语言 程序设计
DISP PROC
MOV AH,2 ; 2号系统功能调用
INT 21H
MOV DL,‘ ’
MOV AH,2
INT 21H
RET
DISP ENDP
CODE ENDS
END START
2,利用存储器传递参数
利用存储器参数传递, 适合于参数较多的情况 。 大多是在数据区建立参数
表, 里面放有子程序所要使用的参数 。 调用程序把该参数表首地址传送给子
程序 。 子程序通过参数表取得所需参数, 在数据处理完后, 将结果也送到指
定的数据储存区中 。
【 例 6.4】 将例 【 例 6.3】 的程序改为用存储器传递参数 。
汇编语言 程序设计
源程序为:
NAME EXAM6_4
DATA SEGMENT
BCDUBF DB 71H,24H,96H,87H,12H,78H,56H,34H,63H,45H
COUNT EQU $-BCDBUF
ASCBUF DB 20 DUP(?)
DATA ENDS
STACK SEGMENT PARA STACK’STACK’
STAPN DB 100 DUP(?)
TOP EQU LENGTH STAPN
STACK ENDS
CODE SEGMENT
ASSUME CS:CODE,DS:DATA,ES:DATA,SS:STACK
START,MOV AX,DATA
MOV DS,AX
MOV ES,AX
MOV AX,STACK
MOV SS,AX
MOV AX,TOP
MOV SP,AX
MOV SI,OFFSET BCDBUF ; BCD码首址
汇编语言 程序设计
MOV DI,OFFSET ASCBUF ; ASCII码首址
MOV CX,COUNT ; 组合 BCD码个数
CLD ; DF=0
LP,LODSB
MOV BL,AL
AND AL,OFH
OR AL,30H
STOSB
CALL DISP
MOV AL,BL
PUSH CX
MOV CL,4
SHR AL,CL
OR AL,30H
STOSB
CALL DISP
POP CX
LOOP LP
MOV AH,4CH
INT 21H
汇编语言 程序设计;子程序名,DISP; 功能:显示 ASCII字符;入口参数,DI指向 ASCII码单元
DISP PROC
PUSH DI
DEC DI ; 该 DI是要显示字符所在单元地址
MOV DL,[DI]
MOV AH,2
INT 21H
POP DI
MOV DL,‘ ’
MOV AH,2
INT 21H
RET
DISP ENDP
CODE ENDS
END START
汇编语言 程序设计
3,利用堆栈传递参数
利用堆栈进行参数传递, 就是在主程序中将参数推入堆栈, 而在子
程序中将参数从堆栈中弹出 。
用堆栈传递参数也适于多参数的情况, 但要注意堆栈后进先出的特点,
避免参数进出栈的混乱 。
【 例 6.5】 将例 【 例 6.3】 的程序改为用堆栈传递参数 。
LP,LODSB
MOV BL,AL
AND AL,OFH
OR AL,30H
MOV AH,0
PUSH AX ; 保存低位 BCD码对应的 ASCII码
STOSB
CALL DISP
MOV AL,BL
MOV DX,CX
汇编语言 程序设计
MOV CL,4
SHR AL,CL
OR AL,30H
MOV CX,DX
MOV AH,0
PUSH AX
STOSB
CALL DISP
LOOP LP
MOV AH,4CH
INT 21H; 子程序名,DISP; 功能:显示 ASCII字符;入口参数,ASCII在堆栈中
DISP PROC
MOV BP,SP
MOV DL,[BP+2] ; 取出 ASCII码字符送入 DL
MOV AH,2
INT 21H
MOV DL,‘ ’
汇编语言 程序设计
MOV AH,2
INT 21H
RET
DISP ENDP
CODE ENDS
END START
请大家注意:前面所介绍的三种参数的传递方法, 并不是固定不变
的, 即它们是可以综合使用的 。 依实现的需要和情况的不同, 可以使
用其中一种方式, 也可以同时使用几种方式的混合 。 有的时候还可能
并不需要参数传递 。
返回
汇编语言 程序设计
6.3.5 子程序调用举例
【 例 6.6】 将一个给定的二进制数按位转换成相应的 ASCII码字符串, 送
到指定的存储单元显示 。 如二进制数 10010011转换成字符串为
‘ 10010011’ 。 要求将转换过程写成子程序, 且子程序应具有较好的
通用性, 而必须能实现对 8位和 16位二进制数的转换 。
入口参数,DX存放待转换的二进制数
CX存放待转换数的位数 ( 8位或 16位 )
DX存放 ASCII码首址
出口参数:转换后字符串放在以 DI作指针的字节存储区中 。
NAME EXAM6_6
DATA SEGMENT
NUM8 DB 93H
NUM16 DW 0ABCDH
ASCBUF DB 20 DUP(0)
DATA ENDS
STACK SEGMENT STACK
DB 200 DUP(0)
STACK ENDS
CODE SEGMENT
汇编语言 程序设计
ASSUME DS:DATA,CS:CODE,SS:STACK
START,MOV AX,DATA
MOV DS,AX
MOV DX,0
MOV DL,NUM8 ; 转换二进制数送 DX
MOV CX,8
LEA DI,ASCBUF ; 字符串首址 → DI
CALL BTASC ; 调用子程序 BTASC
MOV [DI],BYTE PTR 0DH
MOV [DI+1],BYTE PTR 0AH
MOV [DI+2],BYTE PTR '$'
LEA DX,ASCBUF
MOV AH,9
INT 21H
MOV DX,NUM16
MOV CX,16 ; 置位数 16
LEA DI,ASCBUF
CALL BTASC
汇编语言 程序设计
MOV [DI],BYTE PTR 0DH
MOV [DI+1],BYTE PTR 0AH
MOV [DI+2],BYTE PTR '$' ; 显示转换后的字符串
LEA DX,ASCBUF
MOV AH,9
INT 21H
MOV AH,4CH
INT 21H
BTASC PROC
PUSH AX ; 保存 AX
MOV AL,0
CMP CX,8 ; 比较 8位数
JNE L1 ; 直接转换 16位数
MOV DH,DL ; 8位数转换送 DH
L1,ROL DX,1 ; DX最高位移入 CF
MOV AL,0
RCL AL,1 ; CF移入 AL最低位
汇编语言 程序设计
ADD AL,30H
MOV BYTE PTR [DI],AL
INC DI
LOOP L1
POP AX
RET
BTASC ENDP
CODE ENDS
END START
【 例 6.7】 已知某班 N个学生的成绩, 试编制一个子程序统计不及格, 60~ 69分,
70~ 79分, 80~ 89分, 90~ 99分及 100分的人数, 分别存放到以 S为首址的单
元中 。
入口参数:以变量 SCORE为首址的字存储单元的值 。
CX存储待处理的学生人 。
出口参数:以 S为首址的字存储区的值 。
汇编语言 程序设计
NAME EXAM6_7
STACK SEGMENT STACK
DB 200 DUP (0)
STACK ENDS
DATA SEGMENT
SCORE DW 78,89,83,54,35,76,74,85,90,100,66,95
N EQU ($-SCORE)/2
S DW 6 DUP (0)
CODE SEGMENT
ASSUME CS:CODE,DS:DATA,SS:STACK
START,MOV AX,DATA
MOV DS,AX
MOV CX,N
CALL COUNT
MOV AH,4CH
INT 21H
COUNT PROC
MOV SI,0
NEXT,MOV AX,SCORE[SI]
CMP AX,60
JB L1
MOV BX,10
DIV BL
汇编语言 程序设计
CBW
MOV BX,AX
SUB BX,5
SAL BX,1
INC WORD PTR S[BX]
JMP L2
L1,MOV BX,0
SAL BX,1
INC WORD PTR S[BX]
L2,ADD SI,2
LOOP NEXT
RET
COUNT ENDP
CODE ENDS
END START
【 例 6.8】 将 AX中的十六位有符号二进制数以十进制形式显示输出子程序 F2T10。
该程序首先判断 AX中数的符号, 若数为负数, 则将负号, -” 送入输出缓冲区, 并求
( AX) 的绝对值;若 AX中的数为正数, 则不做其他处理, 此时 ( AX) 即为无符号二
进制数 。 然后将无符号二进制数转换成十进制数, 可采用将 ( AX) 除以 10,得到第
一个商和第一个余数, 第一个余数就是所求十进制数的个数;将第一个商除以 10,
得到第二个商和余数, 第二余数就是所求十进制数的十位数 ……,这一过程一直循环
到商数为 0时,
汇编语言 程序设计
得到的余数就是所求十进制数的最高位数, 为了得到转换后的十进制数 ASCII字
符串, 可利用堆栈的先进后出原则来实现 。
NAME EXAM6_8
DATA SEGMENT
BINARY DW 7FFFH,0,0FFFEH,50H,8000H,1000H,2000H
N=($-BINARY)/2
DATA ENDS
STACK SEGMENT STACK
DB 200 DUP (0)
STACK ENDS
CODE SEGMENT
ASSUME CS:CODE,DS:DATA,SS:STACK
START,MOV AX,DATA
MOV DS,AX
MOV CX,N
LEA DI,BINARY
LOPA,MOV AX,[DI]
CALL F2T10
MOV DL,','
MOV AH,2
汇编语言 程序设计
INT 21H
ADD DI,2
LOOP LOPA
MOV AH,4CH
INT 21H
DATA SEGMENT
BUF DB 7 DUP (?)
DATA ENDS
F2T10 PROC
PUSH BX
PUSH DX
PUSH SI
PUSH CX
LEA SI,BUF
OR AX,AX
JNS PLUS
NEG AX
MOV [SI],BYTE PTR '-'
INC SI
PLUS,MOV BX,10
MOV CX,0
LOP1,MOV DX,0
DIV BX
PUSH DX
汇编语言 程序设计
INC CX
OR AX,AX
JNE LOP1
LOP2,POP AX
CMP AL,10
JB L1
ADD AL,7
L1,ADD AL,30H
MOV [SI],AL
INC SI
DEC CX
JNE LOP2
MOV [SI],BYTE PTR '$'
LEA DX,BUF
MOV AH,9
INT 21H
POP CX
POP SI
POP DX
POP BX
RET
F2T10 ENDP
CODE ENDS
END START
返回
汇编语言 程序设计
6.3.6 子程序的嵌套与递归
1,子程序的嵌套
一个程序可以调用一个或多个子程序 。 那么一个子程序是否也可以调用
另一个子程序呢? 回答是肯定的 。 这种一个子程序调用另一个子程序
的情况, 称为子程序的嵌套 。
主程序
MA
子程序 S1 子程序 S2
CALL S1
END
S1 PROC
CALL S2
RET
S2 PROC
RET
图 6.1 子程序嵌套示意图
汇编语言 程序设计
2,子程序的递归调用
子程序的递归调用是指一个子程序直接或间接地调用自己 。 递归子程序一
般对应于数学上对函数的递归定义, 它往往能设计出效率较高的程序, 完成
相当复杂的计算, 递归调用要注意必须有结束递归调用的判断语句 。 这里以
阶层函数为例, 说明递归子程序的设计方法 。
STACK SEGMENT STACK
DB 200 DUP(0)
STACK ENDS
DATA SEGMENT
N DW 5
RESULT DW?
DATA ENDS
CODE SEGMENT
ASSUME CS:CODE,SS:STACK,DS:DATA
START,MOV AX,DATA
MOV DS,AX
MOV AX,N
CALL FACT
MOV AX,RESULT
MOV AH,4CH
汇编语言 程序设计
INT 21H
FACT PROC
CMP AX,0
JNE L1
MOV RESULT,1
JMP EXIT
L1,PUSH AX
DEC AX
CALL FACT
POP AX
MUL RESULT
MOV RESULT,AX
EXIT,RET
FACT ENDP
CODE ENDS
END START 返回