1/37 第第第 第第第第第 6.1 第第第第第第第第 第第 第第第第 第第第第第 () procedure_name PROC NEAR ( FAR ) …… procedure_name ENDP 第第第第第第第 第第第第第第第 , 第第第第。 第第第第第第第第第第第第第第第第第第 第第第 第第第第第第第第第第第第第第 ,一, 第第第第第第 第第第第第第第第第第第第第第 第第第第第第第第第 第第第第第第第第 ,,
Jan 27, 2016
1/37
第六章 子程序结构6.1 子程序的设计方法
过程(子程序)定义伪操作
procedure_name PROC NEAR ( FAR ) ……procedure_name ENDP
过程名为标识符,是子程序入口的符号地址。
把功能相对独立的程序段单独编写和调试,作为一个相对独立的模块供程序使用,就形成子程序
子程序可以实现源程序的模块化,可简化源程序结构,可以提高编程效率
2/37
过程属性:( 1 ) NEAR 属性:调用程序和子程序在同一代码段中 (段内调用)
( 2 ) FAR 属性:调用程序和子程序不在同一代码段中 (段间调用)
对简化段定义格式,在微型、小型和紧凑存储模式下,过程的缺省属性为 near ;在中型、大型和巨型存储模式下,过程的缺省属性为 far
对完整段定义格式,过程的缺省属性为 near
用户可以在过程定义时用 near 或 far 改变缺省属性
3/37
code segment main proc far ...... call subr1 ...... retmain endp
subr1 proc near ...... retsubr1 endp
code ends
code segment main proc far ...... call subr1 ...... ret
subr1 proc near ...... retsubr1 endp
main endpcode ends
例 6.1 调用程序和子程序在同一代码段中
一般主程序 MAIN
应定义为 FAR 属性
过程定义可嵌套
4/37
例 6.2 调用程序和子程序不在同一代码段中
segx segment subt proc far ...... ret subt endp ...... call subt ;段内调用 ...... segx ends
segy segment ...... call subt ;段间调用 ...... segy ends
5/37
保存与恢复寄存器 由于调用程序和子程序经常是分别编制的,所以它们使用的寄存器往往会发生冲突。
subt proc far push ax push bx push cx push dx ...... ;过程体 pop dx pop cx pop bx pop ax ret ;过程返回subt endp
主程序中使用的寄存器内容保留在堆栈中
在退出子程序前把寄存器内容恢复原状。
通常,子程序中用到的寄存器是应该保持的。如果使用寄存器在主程序和子程序之间传送参数的话,则不需要保留和恢复的过程。
6/37
子程序调用:隐含使用堆栈保存返回地址
call near ptr subp (段内) (1) 保存返回地址 (2) 转子程序 (IP) ← subp 的偏移地址
call far ptr subp (段间)
(1) 保存返回地址 (2) 转子程序
(CS) ← subp 的段地址 (IP) ← subp 的偏移地址
(IP)(SP)→
(IP)(SP)→
(CS)
子程序的调用和返回
7/37
(IP)(SP)→
(CS)
(flags)
int n ( n : 中断类型号) (1) 保存现场和返回地址 (2) 转中断处理程序 (IP) ← (n*4) (CS) ← (n*4+2)
子程序返回(中断返回):
( 1 ) ret
( 2 ) iret
int 21H P103
8/37
子程序的变量(参数)传送:调用程序在调用子程序时需要传送一些参数给子程序,子程序运行后也经常需要回送一些信息给调用程序。
入口参数(输入参数):主程序提供给子程序 出口参数(输出参数):子程序返回给主程序 参数的形式:
① 数据本身(传值)② 数据的地址(传址)
传递的方法:① 寄存器 ② 变量 ③ 堆栈
9/37
参数传送方式:
( 1 )通过寄存器传送变量
( 2 )子程序和调用程序在同一程序模块中,则子程序可直接访问模块中的变量。(通过存储器传送变量)
( 3 )通过地址表传送变量地址
( 4 )通过堆栈传送变量或变量地址
( 5 )子程序和调用程序不在同一程序模块中,则有两种传送方式:建立公共数据区和使用外部符号。 ( 通过存储器传送变量 )
10/37
例 6.3 十进制到十六进制的转换程序(通过寄存器传送变量)
decihex segment ; 1016 assume cs: decihex
main proc far push ds sub ax, ax push axrepeat: call decibin ; 102 ,取数存在 BX 中 call crlf ;显示回车和换行 call binihex ; 216 ,用十六进制显示 BX 中的数 call crlf ;显示回车和换行 jmp repeat retmain endp
11/37
decibin proc near ; 102 ,存数在 BX 中 mov bx, 0newchar: mov ah, 1 ; AH=1 时,是带回显的键盘 int 21h ;输入, AL=输入字符
sub al, 30h jl exit ; <0 退出 cmp al, 9d jg exit ; >9 退出 cbw ; AL符号扩展到 AX xchg ax, bx mov cx, 10d mul cx xchg ax, bx add bx, ax jmp newchar
exit: retdecibin endp
12/37
binihex proc near ; 216 ,参考例 5.1 mov ch, 4 ; CH为循环次数rotate: mov cl, 4 ; CL为移位次数 rol bx, cl mov al, bl and al, 0fh add al, 30h cmp al, 3ah jl printit add al, 7h ; ‘A’~’F’ printit: mov dl, al mov ah, 2 ; AH=2 ,为显示输出 int 21h dec ch jnz rotate retbinihex endp
13/37
crlf proc near mov dl, 0dh mov ah, 2 int 21h ;显示输出回车 mov dl, 0ah mov ah, 2 int 21h ;显示输出换行 retcrlf endp
decihex ends end main
14/37
例 6.4 累加数组中的元素(直接访问变量)data segment ary dw 1,2,3,4,5,6,7,8,9,10 ;数组初值 count dw 10 ;个数 sum dw ?data endscode segmentmain proc far assume cs:code, ds:datastart: push ds sub ax, ax push ax mov ax, data mov ds, ax call near ptr proadd retmain endp
15/37
proadd proc near push ax push cx ;保留寄存器 push si lea si, ary mov cx, count;循环次数 xor ax, ax ; ax 寄存器清零
next: add ax, [si] add si, 2 loop next mov sum, ax pop si pop cx ;恢复寄存器 pop ax ret
proadd endpcode ends end start
16/37
data segment
ary dw 1,2,3,4,5,6,7,8,9,10count dw 10sum dw ?
ary1 dw 10,20,30,40,50,60,70,80,90,100count1 dw 10sum1 dw ?
data ends
如果数据段定义如下:
* 如果直接访问内存变量,那么累加数组 ary 和数组 ary1中的元素不能用同一个子程序 proadd
17/37
例 6.4 累加数组中的元素(通过地址表传送变量地址)data segment ary dw 10,20,30,40,50,60,70,80,90,100 count dw 10 sum dw ? table dw 3 dup (?) ; 地址表data ends
code segmentmain proc far assume cs:code, ds:datastart: push ds sub ax, ax push ax mov ax, data mov ds, ax mov table, offset ary ;首地址放在 table mov table+2, offset count ; count 的地址放在 table+2 mov table+4, offset sum ; sum 的地址放在 table+4 mov bx, offset table ;地址表的首地址通过 bx 传送 call proadd ret main endp
在主程序中建立一个地址表,把要传送给子程序的参数放在地址表中
18/37
proadd proc near push ax push cx push si push di mov si, [bx] mov di, [bx+2] mov cx, [di] mov di, [bx+4] xor ax, ax
next: add ax, [si] add si, 2 loop next mov [di], ax pop di pop si pop cx pop ax ret
proadd endpcode ends
end start
ary 10 0000
20 0002 30 40 50 60 70 80 90 100
count 10 0014
sum ? 0016 table 0000 0018 (bx)
0014
0016
(si)
(di)
19/37
data segmentary dw 1,2,3,4,5,6,7,8,9,10count dw 10sum dw ?ary1 dw 10,20,30,40,50,60,70,80,90,100count1 dw 10sum1 dw ?table dw 3 dup (?) ;地址表data ends
如果数据段定义如下:
代码段中两次调用 proadd mov table, offset ary mov table+2, offset count mov table+4, offset sum mov bx, offset table call proadd
mov table, offset ary1 mov table+2, offset count1 mov table+4, offset sum
1 mov bx, offset table call proadd
20/37
例 6.4 累加数组中的元素(通过堆栈传送变量地址)
data segment ary dw 10,20,30,40,50,60,70,80,90,100 count dw 10 sum dw ?data ends
stack segment dw 100 dup (?) tos label word ;栈底类型stack ends
21/37
code1 segmentmain proc far assume cs:code1, ds:data, ss:stackstart: mov ax, stack mov ss, ax mov sp, offset tos push ds sub ax, ax push ax mov ax, data mov ds, ax
mov bx, offset ary push bx ;数组首地址入栈 mov bx, offset count push bx ;数组个数地址入栈 mov bx, offset sum push bx ;和地址入栈
call far ptr proadd ret
main endpcode1 ends
22/37
code2 segment assume cs: code2proadd proc far
push bp mov bp, sp
push ax push cx push si push di
mov si, [bp+0ah] mov di, [bp+8] mov cx, [di] mov di, [bp+6]
(bp)+0a 0000
(sp) (di) (si) (cx) (ax)
(bp) (bp) (ip) (cs)
(bp)+6 0016
(bp)+8 0014
0
(ds)
xor ax, axnext: add ax, [si]
add si, 2 loop next mov [di], ax
pop di pop si pop cx pop ax
pop bp
ret 6proadd endpcode2 ends end start
Call 调用
23/37
从连接的角度看,把用户定义的符号分为外部符号和局部符号
局部符号:在模块中定义,又在本模块中使用的符号。
外部符号:在模块中定义,可在另一个模块中使用的符号。
外部符号定义的两种方式:
PUBLIC 伪操作:
功能:在一个模块中定义的符号可提供给其他模块使用
格式: PUBLIC symbol[ ,…… ]
EXTRN 伪操作:
功能:在另一个模块中定义的符号,而在本模块中使用该符号。
格式: EXTRN symbol name : type [ ,…… ]
其中符号可以是变量、标号、过程名等。
24/37
增强功能的过程定义伪操作
格式:过程名 PROC [ 属性字段 ] [USES字段 ] [ ,参数字段 ]
……
过程名 ENDP 属性字段组成:
★distance :类型属性 NEAR 或 FAR
★language type :说明该过程可作为某高级语言程序的子过程
★visibility :说明该过程的可见性,可用 private 或 public 。
private 使该过程的可见性只能在当前的源文件。
public 则允许其他模块调用该过程。
★prologue :是一个宏名,允许用宏来控制过程的入口和出口
25/37
USES字段:允许用户指定所需要保存和恢复的寄存器表
MASM将在过程入口自动生成 push指令来保存这些寄存器,并在过程出口的 ret指令前自动生成 pop指令来恢复这些寄存器。
参数字段:允许用户指定该过程所用参数
格式:参数名:类型 [ ,参数名:类型 ]
MASM规定,在过程内可用 LOCAL 为局部变量申请空间。 格式: LACAL 变量定义 [ ,变量定义 ]
变量定义格式: label ;默认为 word 型
label : type
label[count] : type ;申请数组 count 为个数
26/37
例 6.4 用增强功能的过程定义伪操作实现.CODE CODE2
PROADD PROC PASCAL USES AX CX SI DI
PARA:WORD,PARC:WORD,PARS:WORD
MOV SI , PARA ; 数组的首地址 ( 第一次入栈的数 )
MOV DI , PARC ;count 的地址 ( 第二次入栈的数 )
MOV CX , [DI]
MOV DI , PARS ;sum 的地址 ( 第三次入栈的数 )
XOR AX , AX
NEXT: ADD AX , [SI]
ADD SI , 2
LOOP NEXT
MOV [DI] , AX
RET
PROADD ENDP
27/37
6.2 子程序嵌套
子程序的嵌套
主程序 子程序 A 子程序 B
proc_A proc_B
…... …… …...
call proc_A call proc_B …...
…… …… ret
ret
子程序的递归:一个子程序调用的子程序是它自身.
28/37
堆栈溢出
堆栈区域是在堆栈定义时就确定了,因而堆栈工作过程中有可能产生溢出.
堆栈上溢:堆栈已满,但还想再存入信息.
堆栈下溢:堆栈已空,但还想取出信息.
29/37
6.3 子程序举例例 6.9 十六进制到十进制的转换程序(通过寄存器传送变量)
hexidec segment ; 1610 main proc far assume cs: hexidecstart:
push ds sub ax, ax push ax
repeat: call hexibin ; 162 ,放在 bx 中 call crlf call binidec ; 210 call crlf jmp repeat ret
main endp
30/37
hexibin proc near ; 162 mov bx, 0newchar:
mov ah, 1 int 21h sub al, 30h jl exit cmp al, 10d jl add_to sub al, 27h ; ‘ a ’ ~ ‘ f ’ cmp al, 0ah jl exit cmp al, 10h jge exit
add_to: mov cl, 4 shl bx, cl mov ah, 0 add bx, ax jmp newchar
exit: rethexibin endp
31/37
binidec proc near ; 210 mov cx, 10000d call dec_div mov cx, 1000d call dec_div mov cx, 100d call dec_div mov cx, 10d call dec_div mov cx, 1d call dec_div ret
binidec endp
dec_div proc near mov ax, bx mov dx, 0 div cx mov bx, dx mov dl, al add dl, 30h mov ah, 2 int 21h retdec_div endp
32/37
crlf proc near mov dl, 0ah mov ah, 2 int 21h mov dl, 0dh mov ah, 2 int 21h retcrlf endp
hexidec ends end start
33/37
DOS 功能调用 INT 21H 用户在程序中调用 DOS 提供的一些子功能:
( 1 )一般设备的输入输出 ( 2 )磁盘的输入输出及磁盘文件的管理 ( 3 )其它 调用方法:
( 1 )设置调用参数( 2 )MOV AH, 功能号( 3 ) INT 21H
34/37
AH=1 :键盘输入并回显(可用 ctrl+c 或 ctrl+break自动退出 )
AH=8 : 不回显键盘输入(可用 ctrl+c 或 ctrl+break自动退出 )
AH=7 :直接键盘输入 ( 不能退出 , 也不回显 )
入口参数:无
出口参数: AL= 输入字符的 ASCII 码
AH=2 :显示输出
入口参数: DL= 输出字符的 ASCII
出口参数:无
AH=9:显示字符串
入口参数: DS:DX=串地址,字符串以‘ $’ 结尾
出口参数:无
35/37
( 1 ) DOS键盘功能调用
例:单字符输入 ( AH=1 )
get-key: mov ah, 1 int 21h cmp al, ‘Y’ je yes cmp al, ‘N’ je no jne get_key yes: …… no: ……
36/37
例:输入字符串 ( AH=0ah ) 定义缓冲区:
( 1 ) maxlen db 32 actlen db ? string db 32 dup (?)
( 2 ) maxlen db 32, 0, 32 dup (?)
( 3 ) maxlen db 32, 33 dup (?)
lea dx, maxlen mov ah, 0ah int 21h
20 maxlen actlen
‘H’
‘O’
‘W’
‘A’
‘R’
‘E’
‘Y’
‘O’
‘U’
20
20
0d
string0b
(DX)
37/37
( 2 ) DOS 显示功能调用 ( AH=2,5 ,9 )
例:显示单个字符 ( AH=2 )mov ah, 2mov dl, ‘A’int 21h
例:显示字符串 ( AH=9 )string db ‘HELLO’, 0dh, 0ah, ‘$’mov dx, offset stringmov ah, 9int 21h
( 3 ) DOS打印功能 ( AH=5 )
DL= 打印字符返回