2009-7-24 80x86汇编语言程序设计第 6章 子程序结构讲授要点子程序的定义、调用与返回。
子程序的参数传递方法。
2009-7-24 80x86汇编语言程序设计
6.1子程序 概述
6.1.1 过程的定义过程定义由 PROC与 ENDP伪指令实现,形式如下:
过程名 PROC [NEAR|FAR]
<过程体 >
过程名 ENDP
1,过程名在整个程序中必须是唯一的 。
2,过程名本质上与标号一样,也具有 3种属性:段地址,
偏移地址和类型 ( NEAR或 FAR) 。
3,PROC后用关键字 NEAR,FAR或空,以表示过程的类型 ( 缺省为 NEAR) 。
2009-7-24 80x86汇编语言程序设计
6.1.2 过程调用和返回
1,过程调用和返回指令
( 1) CALL:过程调用与 JMP指令类似,CALL指令包括下列 4种调用方式:
段内直接调用 ( Intrasegment/Direct Call)
段间直接调用 ( Intersegment/Direct Call)
段内间接调用 ( Intrasegment/IndirectCall)
段间间接调用 ( Intersegment/Indirect Call)
段内调用在同一代码段内进行,又称近 ( Near) 调用;
段间调用可以在不同代码段之间进行,又称远 ( Far) 调用 。
2009-7-24 80x86汇编语言程序设计语法格式:
CALL ProcName
段内直接调用,IP进栈,IP= label的偏移地址;
段间直接调用,CS:IP进栈,CS:IP= label的分段地址
CALL reg16/mem16
段内间接调用,IP进栈,IP= reg16 / [mem16]
CALL mem32
段间间接调用,CS:IP进栈,CS = mem32高字,IP= mem32低字功能描述:
( 1) 返回地址进栈 。
远调用,CS与 IP( 下一条指令的地址 ) 依次进栈 。
近调用,IP( 下一条指令的 16位偏移地址 ) 进栈 。
( 2) 转移到过程的第 1条指令去执行 。
远调用:根据操作数,将 32位地址送 CS:IP。
近调用:根据操作数,将 16位偏移地址送 IP。
对标志位的影响:无 。
2009-7-24 80x86汇编语言程序设计
( 2) RET指令 RET( Return),过程返回语法格式:
RET ; 近返回或远返回
RET imm16 ; 近返回或远返回,并调整堆栈,SP = SP +
imm16
功能描述:
RET:返回地址出栈,从而实现转移到返回地址处 。 其中,
远返回,POP1个双字到 CS:IP。
近返回,POP1个字到 IP。
RET imm16:在返回地址出栈后,CPU立即将 imm16加到堆栈指针 SP。 这种机制用来在返回前将参数从栈中移出 。
对标志位的影响:无 。
说明,RET由汇编程序根据其所在过程的类型 ( NEAR或 FAR)
决定是近返回还是远返回 。 缺省为近返回 。
2009-7-24 80x86汇编语言程序设计
2,使用过程应注意的问题
在过程体内必须有一条 RET指令被执行到 。 如果在过程内没有执行到 RET或其它转移指令,程序将继续执行 ENDP后的指令 。
正确选择过程的类型 。 通常基于下列原则:
若过程只在同一代码段中被调用,则定义为 NEAR。
若过程可以在不同代码段中被调用,则定义为 FAR。
通常要保证 RET指令执行前,栈顶内容正好是返回地址 。
注意保护相关寄存器的值 。 通常,除了作为返回参数的寄存器外,过程不应改变其它寄存器的值 。
可以将过程定义放在单独的代码段中 。 若过程定义与主程序处于同一代码段,则要保证其只有被调用时,才会执行 。
2009-7-24 80x86汇编语言程序设计
3,保存和恢复寄存器例,SUBT PROC NEAR
PUSH AX
PUSH BX
PUSH CX
……
POP CX
POP BX
POP AX
RET
SUBT ENDP
2009-7-24 80x86汇编语言程序设计
【 例 6.1】 分析下列程序,描述它的功能。
dseg SEGMENT
buf DB 80,81 DUP(0)
dseg ENDS
sseg SEGMENT STACK
DW 64 DUP(0)
sseg ENDS
cseg SEGMENT
ASSUME CS:cseg,DS:dseg,SS:sseg
2009-7-24 80x86汇编语言程序设计
cr PROC ( NEAR)
MOV AH,2
MOV DL,13
INT 21H
MOV DL,10
INT 21H
RET
cr ENDP
2009-7-24 80x86汇编语言程序设计
main:MOV AX,dseg
MOV DS,AX
LEA DX,buf
MOV AH,10
INT 21H ;输入一个符号串
CALL cr
MOV AH,1
INT 21H ;输入一个字符
MOV BL,AL ;用 BL保存读入的字符
2009-7-24 80x86汇编语言程序设计
lab2,MOV DL,[SI]
CMP DL,BL
JZ lab1 ;等于第 2次输入的符号则转
MOV AH,2
INT 21H
INC SI
LOOP lab2
lab1:MOV AH,4CH
INT 21H
cseg ENDS
END main
2009-7-24 80x86汇编语言程序设计
【 例 6.2】 编写一个子程序,对一个无符号的字型数组的各元素求和。在调用子程序之前,已把数组的段地址放在 DS中,起始偏移地址放在寄存器 SI
中,数组元素个数 (>0)放在 CX中。要求子程序把计算结果以双字的形式存放,高位放在 DX中,低位放在 AX中。
sum PROC NEAR
PUSH BX ;保护用到的寄存器 BX
XOR AX,AX
MOV DX,AX ;求和前先把存放结果的 DX,AX清 0
MOV BX,AX
2009-7-24 80x86汇编语言程序设计
s1,ADD AX,[BX+SI] ;把一个元素加到 AX中
ADC DX,0 ;若有进位,DX加 1
INC BX
INC BX ;BX加 2,指向数组的下一元素
LOOP s1
POP BX ;恢复寄存器 BX的值
RET
sum ENDP
2009-7-24 80x86汇编语言程序设计
6.2 过程的参数传递参数的分类:
入口参数:由调用者向过程传递的数据,作为过程的输入参数 。
出口参数:由过程向调用者返回的数据,作为过程的输出参数 。
根据问题的需要,过程可以只有入口参数或只有出口参数,也可以二者兼有 。
对于过程与调用者之间的参数传递,可根据传递的数据量,选择采用寄存器,变量或堆栈等方式 。 由于过程是相对独立的功能块,因此,
在定义过程时,通常要加上适当的注释,主要包括功能,入口参数与出口参数等 。
2009-7-24 80x86汇编语言程序设计
6.2.1 用变量传递参数在程序中定义全局变量,如放在数据段,过程直接按名访问该变量 。
过程直接以变量作为参数,虽然方便,但通用性较差 。
【 例 6.3】 编写一个子程序,以放在 DS段中 year的公元年份为入口参数,判断该年是否为闰年。
另有一个应用程序,它已定义了一个字节型数组 t,依次存放着
12个月的每月天数,其中 2月份的天数是 28。应用程序已经在 DS段中存放了年份值,利用前面编写的子程序,编写程序段调整数组 t中
2月份的天数。
2009-7-24 80x86汇编语言程序设计
【 解 】;功能:根据一个年份是否为闰年,设置该年 2月份的天数;入口,DS段中的字型变量 year = 公元年份;出口,DS段中的字节型变量 t+1= 该年 2月份天数;破坏寄存器:无
jud1 PROC NEAR
PUSH AX
PUSH BX
PUSH CX
PUSH DX
MOV BYTE PTR [t+1],28
MOV AX,[year]
2009-7-24 80x86汇编语言程序设计
MOV DX,0
MOV BX,4
DIV BX ;除以 4
CMP DX,0
JNZ lab1 ;不能整除 4则不是闰年,转
MOV AX,[year] ;取回年份值
MOV BX,100
DIV BX ;除以 100
CMP DX,0
JNZ lab2 ;不能整除 100则是闰年,转
MOV AX,[year]
MOV BX,400
DIV BX ;除以 400
2009-7-24 80x86汇编语言程序设计
CMP DX,0
JZ lab2
lab2,INC BYTE PTR [t+1] ;是闰年,把天数加 1,设置出口参数
lab1,POP DX
POP CX
POP BX
POP AX
RET
Jud1,ENDP
2009-7-24 80x86汇编语言程序设计
6.2.2 用寄存器传递参数通过寄存器传递数据或数据地址 。
通常选择 AL,AX,DX:AX( 或 EAX) 传递字节,字或双字 。
传递 16位偏移地址最好选择 SI,DI或 BX,
传递 32位地址可以用 DS:BX,DS:SI,DS:DI,ES:BX,ES:SI或 ES:DI
等 。
2009-7-24 80x86汇编语言程序设计
【 例 6.4】 用寄存器传递参数,编写例 6.3要求的子程序。
【 解 】;功能:判断一个年份是否为闰年;入口,AX = 公元年份;出口,CF,1表示是闰年,0表示非闰年;破坏寄存器,AX
jud PROC NEAR
PUSH BX
PUSH CX
PUSH DX
MOV CX,AX ;临时保存年份值
MOV DX,0
MOV BX,4
2009-7-24 80x86汇编语言程序设计
DIV BX ;除以 4,为预防溢出,用双字除以字
CMP DX,0
JNZ lab1 ;不能整除 4则不是闰年,转
MOV AX,CX ;取回年份值
MOV BX,100
DIV BX ;除以 100
CMP DX,0
JNZ lab2 ;不能整除 100则是闰年,转
MOV AX,CX
MOV BX,400
DIV BX ;除以 400
CMP DX,0
JZ lab2
2009-7-24 80x86汇编语言程序设计
lab1,CLC ;把 CF清 0表示非闰年,设置出口参数
JMP lab3
lab2,STC ;把 CF置 1表示是闰年,设置出口参数
lab3,POP DX
POP CX
POP BX
RET
jud ENDP
对于 DX中存放的年份值,需要先放到 AX中,才能调用子程序 jud,
然后以调用返回后的 CF值决定是否把 t数组中表示 2月份天数的 [t+1]加
1。 程序段如下:
MOV AX,DX
CALL jud
ADC BYTE PTR [t+1],0 ;原值+ 0+ CF
2009-7-24 80x86汇编语言程序设计
6.2.3 用地址表传递参数建立一个地址表,存放所有参数的地址,传递地址表的首地址给过程 。
这种方法特别适合于参数较多的情况 。
2009-7-24 80x86汇编语言程序设计
6.2.4 用堆栈传递参数过程从堆栈得到入口参数,返回前将出口参数写入堆栈;调用者通过出栈得到返回参数 。
过程从堆栈存取参数时,通常使用 BP,因为其隐含的段地址在 SS中 。
采用堆栈传递参数时,典型的过程结构如下:
StdProc proc near
push bp
mov bp,sp ; BP指向当前栈顶,用于取入口参数
...
pop bp
ret ParmSize ; 返回前从堆栈移出入口参数
StdProc endp
其中,ParmSize是过程被调用前进栈的入口参数的字节数 。
2009-7-24 80x86汇编语言程序设计
【 例 6.5】 用堆栈传递入口参数,编写子程序,把接收的两个带符号整数中大的一个作为结果,出口参数放在 AX中。
【 解 】;功能:求两个带符号整数中大的一个;入口参数:调用前把两个带符号整数入栈;出口参数,AX;破坏寄存器,AX
2009-7-24 80x86汇编语言程序设计
_max PROC NEAR
PUSH BP ;暂时保存寄存器 BP的值
MOV BP,SP
MOV AX,WORD PTR [BP+6] ;取第 1个参数到 AX
CMP AX,WORD PTR [BP+4] ;与第 2个参数比较
JGE lab
MOV AX,WORD PTR [BP+4] ;取第 2个参数到 AX
lab,POP BP ;恢复寄存器 BP的原值
RET
_max ENDP
2009-7-24 80x86汇编语言程序设计
6.3 子程序举例
【 例 】 编写子程序 write,把整型数据以十进制形式显示到屏幕上。
【 分析 】 参照高级语言中输出语句的功能,write子程序应具备这样一些特点:被显示的整数可以是无符号的,也可以是带符号的,
但需要明确指出是哪一种情况;整数在计算机内部是字型数据,
范围为- 32768~ +65535;被输出的数据是带符号数时,负号
“-”必须输出,而正号“+”总是省略;输出数据的最大位数是十进制的 5位,当计算出 5位中的某一位是 0时,需要判断这个 0
是否应该输出,输出条件是前面已经输出过非 0数字或者这个 0是个位数。输出的数必须是以 ASCII码形式存放在 DL中。
2009-7-24 80x86汇编语言程序设计
【 解 】; 功能,在屏幕上输出整数值; 入口,AX = 待输出的整数; CF= 为 0表示输出无符号数,为 1则输出带符号数; 出口,无; 破坏寄存器,无; DX,AX-存放整数,BX-分离各整数位时除数,CX-分离各数位次数,; SI-表示是否输出过非 0数字,DI-暂存输出的整数
write PROC NEAR
PUSH BX
PUSH CX
PUSH DX
PUSH SI
2009-7-24 80x86汇编语言程序设计
PUSH DI
MOV SI,0 ;SI清 0表示还没有输出过非 0数字
MOV DI,AX ;保存待输出的数值到 DI中
JNC w1 ;作为无符号数输出转
CMP AX,0
JGE w1 ;AX是正数转
MOV DL,'-'
MOV AH,2
INT 21H ;输出负号
NEG DI ;取绝对值放在 DI中
2009-7-24 80x86汇编语言程序设计
w1,MOV BX,10000 ;第一次的除数
MOV CX,5 ;重复次数
w2,MOV AX,DI ;取回待输出数值
MOV DX,0 ;被除数高位清 0
DIV BX ;做双字除以字的除法
MOV DI,DX ;余数保存在 DI中
CMP AL,0
JNE w3 ;商非 0转
CMP SI,0 ;商是 0,判断前面是否输出过数字
JNE w3 ;前面已输出过数字,则当前的 0应该输出,转
CMP CX,1 ;判断是否是个位
JNE w4 ;不是个位则不输出当前的 0,转
2009-7-24 80x86汇编语言程序设计
w3,MOV DL,AL
ADD DL,30H
MOV AH,2
INT 21H ;输出当前这一位数字
MOV SI,1 ;用 SI记载已输出过数字
w4,MOV AX,BX
MOV DX,0
MOV BX,10
DIV BX
MOV BX,AX ;bx / 10 => bx,计算下一次的除数
LOOP w2
2009-7-24 80x86汇编语言程序设计
POP DI
POP SI
POP DX
POP CX
POP BX
RET
write ENDP
2009-7-24 80x86汇编语言程序设计
【 例 6.11】 编写子程序 read,从键盘上读入一个整数 。
【 分析 】 为了尽可能与高级语言中整数输入的情况一致,子程序不仅要能读入正确输入时的数据,还要能对不正确的输入做出适当的反应,因此设计上要注意几个问题:首先是要用字符串输入方式
(DOS的 10号子功能 ),因为这种方式支持退格键修改功能,因而需要准备相应的输入缓冲区;出口参数需要两个,以 CF的设置表示输入是否正确,当输入正确时把整数值放在 AX中作为输入结果;要能够跳过若干个连续的空格符;要能够处理正负号 。
2009-7-24 80x86汇编语言程序设计
【 解 】; 功能,从键盘读入整数值; 入口,CF= 为 0表示废弃多余符号 。; 为 1则把多余符号留作下一次输入 。; 出口,CF= 0表示正常读入,1表示输入有错; 破坏寄存器,无
read PROC NEAR
PUSH BX
PUSH CX
PUSH DX
PUSH SI
PUSH DS ;以上为寄存器保护
2009-7-24 80x86汇编语言程序设计
PUSHF
PUSH CS
POP DS ;令 DS取 CS的值
rd1,MOV BX,CS:[point] ;取上次输入后已读取到输入串的位置
rd2,INC BX
CMP CS:[bufin+BX+1],' '
JE rd2 ;跳过空格
CMP CS:[bufin+BX+1],13
JNZ rd4 ;不是回车键,转读入数值处理
2009-7-24 80x86汇编语言程序设计
rd3,LEA DX,CS:[bufin]
MOV AH,10
INT 21H ;遇回车键要求再次输入
MOV AH,2
MOV DL,10
INT 21H ;换行
MOV CS:[point],0
JMP rd1 ;对新的输入再转去跳过前导空格
rd4,MOV SI,BX
DEC SI ;令 SI指向输入串的第一个有效字符
MOV AX,0
MOV BX,10
MOV CX,0
2009-7-24 80x86汇编语言程序设计
rd5,CMP CS:[bufin+SI+2],'+'
JNZ rd6 ;不是正号转
CMP CL,1
JE rd10 ;已读到正确数值后,遇正号转
CMP CL,0
JE rd8 ;正号是第一个有效字符转
STC ;输入有错
JMP rd13
2009-7-24 80x86汇编语言程序设计
rd6,CMP CS:[bufin+SI+2],'-'
JNZ rd9
CMP CL,1 ;已读到正确数值后,遇负号转
JE rd10
CMP CL,0
JE rd7 ;负号是第一个有效字符转
STC ;输入有错
JMP rd13
2009-7-24 80x86汇编语言程序设计
rd7,MOV CH,1 ;记下读入的是负数
rd8,MOV CL,2 ;记下已读入正 /负号
INC SI ;指向下一字符
JMP rd5
rd9,CMP CS:[bufin+SI+2],'0'
JB rd10 ;不是数字转
CMP CS:[bufin+SI+2],'9'
JA rd10 ;不是数字转
MUL BX ;已读入的数值 × 10
MOV DL,CS:[bufin+SI+2]
SUB DL,30h
MOV DH,0
ADD AX,DX ;乘以 10后加上个位数字
MOV CL,1 ;记下已读入正确数值
INC SI ;指向下一字符
JMP rd5
2009-7-24 80x86汇编语言程序设计
rd10,CMP CL,1
JZ rd11 ;已读入正确数值转
STC ;输入有错
JMP rd13
rd11,CMP CH,1
JNZ rd12 ;已读入的数是正数转
NEG AX ;处理负数
rd12,CLC ;置正确读入标志
rd13,MOV CS:[point],SI ;记下读完后的位置,供下次读入使用
POP BX ;取回进入子程序时入栈保护的
PSW,送 BX
PUSHF ;当前的 PSW入栈保存
TEST BX,1 ;判断进入子程序时的 CF值
JNZ rd14 ;CF为 1,保留多余符号转
MOV CS:[bufin+2],13
MOV CS:[point],0
2009-7-24 80x86汇编语言程序设计
rd14,POPF ;取回入栈保存的 PSW
POP DS ;以下恢复各寄存器值并返回
POP SI
POP DX
POP CX
POP BX
RET
bufin DB 128,0,13,127 dup(0) ;键盘输入缓冲区
point DW 0 ; 用于记载下一次的读取位置
read ENDP
2009-7-24 80x86汇编语言程序设计本章小结
过程的特点是一次定义,多次调用 。 对过程的合理运用,不仅可以缩短源程序的长度,更重要的是可显著改善程序的结构 。
具有独立功能的通用性过程可为多个问题所利用 。
过程的参数传递方法主要有变量,寄存器,堆栈,
地址表等,这些方法也可结合使用 。