分析问题
寻找解决问题的
思路、方法、算法
描述算法
编写程序实现算法
设计测试数据
和预期计算结果
上机调试程序
第四章 汇编语言 程序设计的基本方法
程序设计的解题基本步骤如下:
顺序, 分支, 循环 程序和 子程
序 的设计是汇编语言程序设计的基
本内容。
在此基础上还要掌握汇编语言
程序设计的基本方法和技巧,包
括 递归子程序 设计,COM和 EXE
格式程序 的结构和特点,多模块
程序 设计方法等。
4.1 程序的几种基本结构
程序的基本结构有三种,顺序结构, 分支结构, 循环结构
顺序结构,按语句的书写顺序依次执行
分支结构,根据不同的条件判断结果,执行不同的程序段,
实现程序分支。
循环结构,由条件是否成立,确定是否重复执行某段程序。
4.2 顺序程序设计
例 4-1 (p 77) 查表求 2的整次幂( 2n,n=0,1,…, 14)
分析:
表地址 =array+2*n 就是存放 2n的单元地址
20 21 22 23 24 ……,214
字编号, 0 1 2 3 4
14
array
字节编号, 0 1 2 3 4 5 6 7 8 9 28
29
注意,2n在表中 (即数组 array中 )的存放规律,n正好是 2n在表中
的 存放字单元的顺序号 。 例, (array+2*3)= (array+6)= 23 =8
4.3 分支程序设计
程序的分支通过转移指令来实现,因此转移指令的操作
应该是能够改变程序执行顺序 。
4.3.1 转移指令
转移指令分为两类,无条件转移指令, 条件转移指令
1 ) 无条件转移指令
指令格式,JMP 标号 无条件 转移到 标号 指向的指令继续执行
无条件转移 段内转移段间转移 直接寻址间接寻址
(不影响任何标志位 )
(1) 段内转移(段内直接寻址)
?方法, 修改 CS:IP
段内转移 只需修改 IP实现转移, 使 IP 标号 所在行的指令
即,( IP) =标号所在行 指令的偏移地址
段内转移分为三种形式:
?段内直接短转移 格式,JMP SHORT 标号
操作,( IP) ← ( IP) +D( 8位偏移量 )
段内短转移
例,JMP SHORT NEXT
?
NEXT,MOV AL,4
MOV指令
D=30H
( IP)
B0
04
…
…
…
存储器
代码段
EB
30
0152H
0120H
0121H
0153H
0122H
?
JMP指令
下一条要执行的指令
段内短转移范围:
以 JMP指令为中心的 -128~ +127字节内
根据 JMP指令的操作
转移目的地址为:
(IP)+D=0122H+30H= 0152H (IP)
操作,( IP) ← ( IP) +D( 8位偏移量 )
?段内直接近转移(段内直接寻址)
格式,JMP NEAR PTR 标号
操作:( IP) ← ( IP) +D( 16位偏移量 ) -32768~ +32767字节范围
例 4-3 段内直接近转移举例
JMP NEAR PTR EXIT
?
EXIT,POP AX
两种转移指令都可以写成简化形式 ( 省略 SHORT和 NEAR),
JMP 标号
?汇编时如何确定标号的类型?
位移量 D= ? 8位 段内 短转移 指令> 8位 段内 直接近转移 指令
? 段内间接转移 ( 段内间接寻址 )
格式,JMP WORD PTR OPR 操作,( IP) ← ( OA)
OA是 由 OPR的寻址方式 确定的寄存器或存储单元 (字类型 ),
OA存的是 目的转移地址。
例, 段内间接转移
JMP BX ;( IP) ← ( BX) =转移地址
间接寻址转移指令的特点是指令中 没有直接给出标号
( 2) 段间转移
实现段间转移时, 转移目的地址由段地址和偏移地址构
成, 因此段间转移需要同时修改 CS和 IP。
?段间直接转移 ( 段间直接寻址 )
格式,JMP FAR PTR 标号
操作,( IP) ← 标号的 偏移地址
( CS) ← 标号所在段的 段地址
例 4-5 段间直接转移
CODEl SEGMENT
?
JMP FAR PTR NEW_SEG
?
CODEl ENDS
CODE2 SEGMENT
?
NEW_SEG,MOV AX,BX
?
CODE2 ENDS
JMP [BX] ;( IP) ← (( BX) ) =转移地址
34100H
32000H
XX
XX
…
…
…
CODE2
CODE1
操作码
21
00
32
NEW_SEG
00
…
? ?
EA
JMP指令 IP
CS
设 NEW_SEG标号的地址为 3200H,2100H
执行 JMP指令后, 程序流程控制
将从 CODEl代码段 CODE2代码段,
实现段间转移操作 。
?段间间接转移 ( 段间间接寻址 )
段间直接转移
格式,JMP DWORD PTR OPR
操作,( IP) ← ( OA)
( CS) ← ( OA+2)
OA是由 OPR的寻址方式所确定
的一个双字单元地址 。
( OA) =转移地址的 偏移地址
( OA+2) =转移地址的 段地址
( 段间间接转移见 P80,例 4-6)
2) 条件转移指令 ( 18条)
基本格式为,J×× 标号 ( ×× 代表一种转移条件 )
条件转移指令 根据检测标志位的状态来判断条件,若条件为
真,则转至标号处执行程序,否则顺序往下执行程序 。
操作:( IP) ← ( IP) +D ( 8位偏移量)
显然条件转移指令 转移范围与 段内短转移 相同
条件转移指令 分三类,
( 1) 一般 条件转移指令(见 P81 表 4-1)
用于某个检测标志位
( 2)用于 无符号数 的条件转移指令 (见 P81 表 4-2)
用于检测无符号数的比较
( 3)用于 带符号数 的条件转移指令 (见 P82 表 4-3)
用于检测带符号数的比较
先执行 影响标志位 的指令( 如算术运算、比较及位测试等
指令 ),再执行适当的条件转移指令检测相应的标志位,根据
条件实现转移。
例,利用条件转移指令 构成循环
?
MOV CX,100 (循环计数)
AGAIN:
?
DEC CX
JNZ AGAIN
?
4.3.2 分支程序设计方法
一般分为两类:
? 条件转移指令 —— 实现程序两路分支
?无条件转移指令 +跳转表 —— 实现程序多路分支
1) 由条件转移指令实现程序分支
例 4-9(p83),数据块传送。如果源数据区与目的数据区位置
没有重叠部分,则正向传送数据块,否则反向传送数据块。
分析:
stg1 stg2
299
存储区无重叠
0
0
stg2
299
存储区有重叠
stg1
正向传送
反向传送
?如何判断存储区是否重叠?
存储区 无重叠的判断条件,
源区结束地址 (stg1+源区长度 -1) < 目的区起始地址 (stg2)
程序及流程图见教材 p83 例 4-9
2) 用无条件转移指令 +跳转表实现程序分支
跳转表便于实现多路分支。
设有若干段分支程序,将每段 分支程序的入口地址 (也称
跳转地址)组成一个连续存放在内存中的表,称为 跳转表 。
若有 n个产品,将这 n个产品的加工处理程序(分支程序)入
口地址(分别为 BR0,BR1,…, BRn-1)组成跳转表,可就利
用跳转表取得 BR0,BR1,…, BRn-1,转移到相应的 分支程序
去处理产品。
由 表地址 就可取得分支程序地址 BRn,再将 BRn送 IP,实现转移。
例,根据学生成绩评定等级,90~ 100分的等级为, A”; 80~ 89
分的等级为, B”; 70~ 79分的等级为, C”,60~ 69分的等级为
,D”,<60分的等级为, E” 。
分析,0~ 59 60~ 69 70~ 79 80~ 89 90 ~ 100
0 1 2 3 4
tab dw br0, br1, br2, br3, br4 (跳转表)
E D C B A
(程序见 P84 例 4-10) next
设 n=2,存放 BR2的表地址为:
表地址 =表首址 + BR2在表中的 位置序号 ? 表元素 占用的 字节数
设( BX) =存放 BR2的单元 表地址
返回
JMP [BX] ; (( BX) ) ( IP) =BR2,实现转移
?对于 (b)中的跳转表,怎样使用 JMP指令实现转移? JMP BX
4.4 循环程序设计
根据条件重复执行一段指令就构成了循环程序结构 。
MOV AX,0 ;累加求和寄存器清 0
MOV BX,OFFSET ARRAY ;数组始址送 BX中
MOV CX,50 ;循环控制计数初始化
AGAIN, ADD AX,[BX]
ADD BX,2 ;数组地址增 2,BX指向下一个元素
DEC CX ;循环控制计数减 1
JNZ AGAIN ;计数不为 0时继续循环
MOV S,AX ;计数为 0时循环结束,和存于 S中
保证循环正常执行和结束的条件:
?正确地 初始化 循环控制计数和初始条件
?正确地 检测 循环条件
?必须保证能 达到循环结束条件 (例如在循环体内有 修改循环
控制值 的指令 )
例,将 ARRAY数组中的 50个数求和,并将和存入字变量 S中 。
指令系统提供了专门用于循环结构的 循环控制指令,更加
简便的地实现循环结构。
4.4.1 循环控制指令
循环控制指令的转移范围为,-128~ +127字节
( 2)等于继续循环指令
格式,LOOPZ/LOOPE 标号
( 3)不等于继续循环指令
格式,LOOPNZ/LOOPNE 标号
LOOP AGAIN
(段内短转移)
格式,LOOP 标号
操作,如图所示
( 1) LOOP指令
(CX)≠ 0?
执行循环体
(CX) (CX) -1
是
否
退出循环
(CX) ≠ 0 且 ZF =1?) ≠ 0 且 ZF=0?
在前面的求和例子中,可用一条循环指令代替两条指令:
DEC CX
JNZ AGAIN
MOV CX,80
MOV SI,-1
MOV AL,?$?
AGAIN,INC SI
CMP AL,STRS[SI]
LOOPNE AGAIN
MOV LEN,SI
4.4.2 循环程序设计方法
控制循环的方法可分为两类:
计数循环 —— 用于循环次数已知的情况
条件循环 —— 用于循环次数不确定的情况
设, STRS DB ?string$ ?
例, 求 存放在 STRS存储区中的 字符串长度。
例 4-16( P90), 将字变量 num中的数按 16位 二进制数位 分离
开, 逐位 转换为 ASCII码, 并从最高有效位开始依次逐位存入
以 binbuf为起始地址的 16个字节单元中 。
设,( num) = 4230H
要求将 4230H= 0 1 0 0 0 0 1 0 0 0 1 1 0 0 0 0 B 逐位分离开
并转换为:
30 31 30 30 30 30 31 30 30 30 31 31 30 30 30 30
先将 ( num) ->( DX),再讨论如何分离开各二进制位并转
换为 ASCII码, 分析如下,
根据上述分析, 将 ( DX) 中的二进制数依次 循环左移 1位,
可以使得 各个数位的分离 ( AND) 和 ASCII码转换 ( ADD) 操
作完全相同, 便于组织循环, 并且 循环次数已知 ( 16次 ) 。
例 4-17( P91), 将 十进制数 ( number) 转换为八进制数
方法, ( number) /8 保留余数, 除法操作 循环次数事先未知,
仅当商为 0时, 循环结束 。 此例为条件循环
此例为计数循环
next
( DX) = 0 1 0 0 0 0 1 0 0 0 1 1 0 0 0 0
1 0 0 0 0 1 0 0 0 1 1 0 0 0 0 0 ( ROL DX,1 )
? 0 0 0 0 0 0 0 1 ( AND?, 1)
0 0 0 0 0 0 0 0
+ 0 0 1 1 0 0 0 0 ( ADD?, 30H)
0 0 1 1 0 0 0 0
30
0 0 0 0 1 0 0 0 1 1 0 0 0 0 0 1 ( ROL DX,1)
? 0 0 0 0 0 0 0 1 ( AND)
0 0 0 0 0 0 0 1
+ 0 0 1 1 0 0 0 0 ( ADD)
0 0 1 1 0 0 0 1
31
0 1 0 0 0 0 1 0 0 0 1 1 0 0 0 0 ( ROL DX,1)
? 0 0 0 0 0 0 0 1 ( AND)
0 0 0 0 0 0 0 0
+ 0 0 1 1 0 0 0 0 ( ADD)
0 0 1 1 0 0 0 0
… …
30 31 30 30 30 30 31 30 30 30 31 31 30 30 30
1.
2.
16.
binbuf 返回30
例 4-18 统计 AX中的二进制数含, 1”的总个数, 程序段如下:
MOV CX,0
AGAIN,AND AX,AX ; ( AX) =0?
JZ QUIT ; 是, 退出循环
SAL AX,1 ; 否,( AX) 的最高位移入 CF位
JNC NEXT ; CF≠1时, 转向 NEXT再次循环
INC CX ; CF=1,计数器 (CX)←(CX) +1
NEXT,JMP AGAIN ; 再次循环
QUIT, ?
上述程序为 当型循环 程序结构,先判断条件,后执行循环 。
课堂练习,
采用循环结构完成自然数 1到 100的求和运算。
next
当型循环与直到型循环,
返回
参考答案 2:
.model small
.code
mov ax,@data
mov ds,ax
xor ax,ax
mov cx,100
again,add ax,cx
loop again
mov ax,4c00h
int 21h
end
参考答案 1:
.model small
.data
num dw 1
.code
mov ax,@data
mov ds,ax
mov ax,0
mov cx,100
again,add ax,num
inc num
loop again
mov ax,4c00h
int 21h
end
( 下述例子可用 5.X 或 6.X 版汇编程序汇编 )
思考题:显示下面数组中的数据:
ARRAY DB 1,3,7,8,5,4,6,8
.model small
.data
ARRAY DB 1,3,7,8,5,4,6,8
LEN EQU $- ARRAY
思考题 参考答案:
.code
mov ax,@data
mov ds,ax
lea bx,ARRAY
mov cx,LEN
again,mov dl,[bx]
add dl,30h
mov ah,2
int 21h
inc bx
loop again
mov ax,4c00h
int 21h
end
4.4.3 多重循环程序设计
例, 分析下述程序的功能,
mov bl,20
delay,mov cx,9801h ;外循环开始
idle,loop idle ;内循环体
dec bl
jnz delay ;外循环结束
设计 多重循环程序 时注意下面两点:
消耗时间 延时功能
(2) 转移指令只能从循环结构
内转到循环结构外
(1) 内层循环必须完全
包含在外层循环内
? ? ? ?
多重循环程序能够解决更复杂的实际问题。
4.5 子程序设计
子程序结构是模块化程序设计的重要基础,其 调用 和 返回 分
别由 CALL和 RET指令完成,子程序设计方法包括 子程序定义,
参数传递,子程序的 嵌套调用 和 递归调用 。
4.5.1 调用与返回指令 ( 不影响标志位)
1) 调用指令
CALL指令 的寻址方式类似 JMP指令,也分为四种调用方式。
( 1)段内直接调用
格式,CALL dst
操作,( SP) ← ( SP) -2
(( SP) +1,( SP) )← ( IP) (返回地址 )
( IP) ← ( IP) +D (子程序入口地址 )
保护断点地址
实现转移
类似段内直接近转移操作
?
CALL max ;调用子程序 max
mov ax,bx ;断点指令
?
max proc near ;子程序入口
?
ret
max endp
D
(IP)->
(IP)+D ->(IP)
例 4-23 段内直接调用 子程序 max
转移到 max?
?保护断点 (mov指令 )地址
(IP)入栈
CALL指令操作,
2)段内间接调用
格式,CALL dst
操作,( SP) ← ( SP) -2,(( SP) +1,( SP) )← ( IP)
( IP) ← ( OA)
(OA是存储器单元地址或寄存器,(OA)=子程序入口的偏移地址 )
例 4-24 段内间接调用 子程序 max
CALL BX ;保护断点,IP← ( BX) = max入口的偏移地址
( 3)段间直接调用
格式,CALL dst
操作,( CS) 入栈
( IP) 入栈
( IP) ← 子程序入口的偏移地址
( CS) ← 子程序入口的段地址
保护断点
例 4-25 段间直接调用 子程序 max
CALL FAR PTR max
操作,?保护断点,CS,IP当前值 (断点地址 )入栈
?实现转移,max入口的 段地址和偏移地址送 CS,IP
4) 段间间接调用
格式,CALL dst 操作, (CS),(IP) 入栈,保护断点
( IP) ← ( OA), ( CS) ← ( OA+2)
OA是双字单元地址, 存放子程序入口地址 。
例 4-26 段间间接调用子程序 max
CALL DWORD PTR [BX]
操作,?保护断点,CS,IP当前值 (断点地址 )入栈
?实现转移,( (IP)← ( (BX)),( CS) ← ( (BX)+2)
BX所指双字单元 ( OA) =max入口的 段地址 和 偏移地址
2) 返回指令 ( 不影响标志位)
子程序执行的最后一条指令必须是 RET指令, 返回到 CALL
指令后的断点处继续执行。
( 1)段内返回
格式,RET
操作,( IP) ← (( SP) +1,( SP))
( SP) ← ( SP) +2
将栈顶的断点地址出栈 IP,返回到断点处继续执行 。
( 2)段内带立即数返回
格式,RET 表达式 ( D )
操作,( IP) ← (( SP) +1,( SP))
( SP) ← ( SP) +2
( SP) ← ( SP) +D ( 16位的偏移量)
例,带立即数返回 举例
push ax ;参数 1入栈
push bx ;参数 2入栈
CALL max ;调用子程序 max
mov ax,bx ;断点指令
?
max proc near ;子程序入口
?
ret
max endp
参数 1
参数 2
IP
SP
SP
SP
4
SP
SP
(参数个数 ?2)
( 3)段间返回
格式,RET
( 4)段间带立即数返回
格式,RET 表达式
段间返回 指令操作与 段内返回 指令指令类似,差别只是:
段间返回指令从堆栈中 连续弹出两个字
第一个字(断点的偏移地址)出栈送 IP
第二个字(断点的段地址)出栈送 CS
4.5.2 子程序的编写方法
子程序的优点:简化程序书写,减小代码长度,并且便于
子程序功能的共享和大型程序的模块化组织。
子程序的类型属性, FAR 和 NEAR
( 1) NEAR型, 调用程序和子程序在同一代码段中
( 2) FAR型, 调用程序和子程序不在同一代码段中 P99-P100
( 2) 编写子程序的一般要求
( 1)保护寄存器
( 2)正确使用堆栈
必须成对地执行 PUSH和 POP指令, 使 RET指令能正确地
弹出断点返回地址。
( 3)编写适当的子程序内部文档
使子程序可读性好、易理解和便于使用
sub1 proc near ;子程序入口
PUSH AX
PUSH BX
PUSH CX
?
POP CX
POP BX
POP AX
ret
sub1 endp
出栈与入栈顺序应当相反
next
?
CALL sub1 ;调用子程序 max
mov ax,bx ;断点指令
?
sub1 proc near ;子程序入口
push ax ;数据 1入栈
push bx ;数据 2入栈
?
pop bx ;数据 1出栈
pop ax ;数据 2出栈
ret
sub1 endp
ax
bx
IP
SP
SP
SP
SP
SP
返回
例:子程序内 必须 成对地 执行 PUSH和 POP指令,才能正常
返回。
IP
( 4)参数传递
参数可以增加子程序的灵活性和通用性。调用程序传送给子
程序的参数称为入口参数,子程序返回给调用程序的结果称为
出口参数。
参数传递一般有如下三种方法:
?用寄存器传递参数,将参数送入约定的寄存器中,用于参数
较少的情况。
?用参数表传递参数,调用程序将参数组成一个参数表传送
给子程序,用于大量参数的传送。
?用堆栈传递参数,调用程序将参数压入堆栈,子程序从堆栈
中取得参数。用于参数多、子程序嵌套调用和递归调用 。
本节分别讨论 用 寄存器传递参数 和用 堆栈传递参数 的方法例子。
例 4-29 ( P104) 在一个带符号数的数组中,选出绝对值最大的
值并确定该值存放单元的位置序号。
选最大值方法的分析:
120 430 -44 …
0 1 2
AX 120
DL 0
BX 430
DH 1
430
1 2
-44
学习要点, ?用寄存器传递参数的方法
?选最大值的方法
例 4-31 程序功能与例 4-29相同, 不同的只是本例利用堆栈
将入口参数传送给子程序 。
学习要点,?用堆栈传递参数的方法
?bp寄存器的使用
?带立即数返回指令的使用( ret n)
array
程序分析如下:
程序执行过程中堆栈的变
化情况如图所示。
元素个数
数组首址
IP
bp
ax
bx
cx
dx
si
di
bp
SP
SP
SP
最大值
位置序号
SP
SP
4.5.3 子程序的嵌套调用与递归调用
( 1) 子程序的嵌套调用
A->B->C
( 2) 子程序的递归调用
A->B->A
A->A
间接递归调用
直接递归调用
子程序的递归调用需要由堆栈传递
参数,每次递归调用时将入口 /出口参数、
寄存器内容以及所有的中间结果保存在
堆栈中 。
优点,简洁、易读 缺点,空间开销大
-> ( si )
-> ( cx )
<- ( ax )
<- (dx)
例 4-32 用递归的方法将一个字符数组中的字符串反序输出,
字符串以 ?$?为结束符。 设 strs db ?ab$ ?
ax
bx
IP0
SP
程序执行过程中堆栈的变化如下,
dx
strs
bp0SPbp
1
bp1+0
+2
+4
+6
+8
+10( a )
SP
bp2
( b )
ax
bx
IP1
dx
strs+1
bp1 bp2+0
+2
+4
+6
+8
+10
SP
bp3
( $ )
ax
bx
IP2
dx
strs+2
bp2 bp3+0
+2
+4
+6
+8
+10
//////////
//////////
//////////
//////////
//////////
//////////
//////////
//////////
//////////
//////////
//////////
//////////
//////////
//////////
//////////
//////////
//////////
//////////
$反向输出, b a
( 1)每次递归调用时,正向依次压入各字符的单元地址。
( 2)逐层返回时,反向逐次弹出各字符的单元地址并输出其
中的字符。
参考答案, ( 用寄存器传递参数 )
.model small
.data
ARRAY dw 1,3,7,8,5,4,6,8
LEN equ ($-ARRAY)/2
sum dw?
.code
.startup
mov si,offset array
mov cx,LEN
call get_sum
mov sum,dx
.exit 0
课堂练习, 定义子程序 get_sum 对下面数组中的数据求和,
ARRAY DW 1,3,7,8,5,4,6,8
入口参数, (si)=数组首址 (cx)=元素个数 出口参数, (dx)=数组和
子程序
get_sum proc
push ax
mov ax,0
again,add ax,[si]
inc si
inc si
loop again
mov dx,ax
pop ax
ret
get_sum endp
end
4.6 DOS系统功能调用
DOS系统功能调用 是 DOS为用户提供的常用功能子程序,
功能包括:
( 1)基本输入 /输出
( 2)磁盘读写管理
( 3)文件与内存管理
每个功能子程序有一个编号,可以按功能编号在汇编浯言
程序中直接调用 (p325)。
1 ) 系统功能调用方法
调用 DOS系统功能调用的步骤:
( 1) 功能号 AH寄存器
( 2) 入口参数 指定寄存器;
( 3) 执行 int 21h 指令, 调用功能子程序完成指定的操作
( 4) 从指定寄存器取得 出口参数
2 ) DOS系统功能调用 举例
功能:从键盘输入字符串并存放到输入缓冲区中
功能号,0AH
入口参数,( ds) =输入缓冲区所在段 段地址
( dx) =输入缓冲区的起始 偏移地址
例 4-35 定义缓冲区 buf,存放调用 0AH功能 从键盘输入的字符串:
buf db 20 ;存放输入字符串的缓冲区长度
db? ;由系统填入实际输入字符串长度
db 20 dup (?) ;存放输入字符串的缓冲区
调用 DOS系统功能调用 0AH,
mov ah,0AH
lea dx,buf
int 21h
如果键盘输入字符串为,ABC?
buf + 0 1 2 3 4 5 6
14 3 41 42 43 0D
分析例 4-36( p114)
4.7 EXE文件与 COM文件
用汇编语言可以编写两种格式的源程序:
格式一,经汇编、连接后产生,EXE可执行文件
格式二,经汇编、连接、转换成,COM可执行文件。
( 1) EXE文件
EXE程序装入内存时的
映像如图 4.25所示。
程序段前缀 PSP的结构:
( 256字节) 表 4-4( p116)
next
程序段前缀 PSP的 80H~FFH:隐含的数据传输区( DTA)
DTA可用于传递命令行参数,其中:
H:> COPY oldfile newfile?
( 80H) = 命令行参数串长度 (从命令名后第一个空格字符算起)
81H单元开始存放 命令行参数串
例如:拷贝文件命令
返回
10 o l d f i l e n e w f i l e
80 81 82 84 86 88 8A 8C 8E 90
DTA
( 2) COM文件
COM程序装入内存时的映像如图 4.25所示。
( 1)代码段、数据
段及堆栈段在同一段,
?64KB。
( 4)所有的子过程应定义为 NEAR类型
( 5)直接用 INT 20H退出程序返回 DOS
( 2)第一条指令的偏
移地址固定为 100H,
即有( IP) =100H。
( 3) SP指向该物理
段末尾,故 不用定义
堆栈段 。
COM源程序的格式:
code segment ;只需定义代码段
assume cs,code,ds,code,es,code
org 100h ;第一条可执行命令必须从 100H开始存放
start,jmp begin
? ;数据定义部分
begin,;程序主体部分
?
int 20h ;用 int 20h指令返回 DOS
code ends
end start
例 4-37( P118) 编写程序 mcr.asm,根据在命令行给出的参数
和目录名,利用 DOS功能调用完成 建立目录, 删除目录 或 改变
工作目录 的功能。
命令格式为, mcr 参数 目录名
参数为,/m— 建立目录 /r— 刪除目录 /c— 改变工作目录
若命令行为,mcr /m newdir
程序执行后将建立一个新目录 newdir。
程序中用到的 DOS功能调用:
( dx) = 目录名串首址
( ah) = 39h (建目录)
3ah(删除目录)
3bh(改变工作目录)
返回参数,
CF=0 操作成功
CF=1 操作失败,(ax)=错误号
学习要点, ?编写 COM程序的方法
?命令行参数的获取方法
?DOS功能调用
H:>mcr /m newdir?
0A / m n e w d i r
80 81 82 84 85 86 88 8A 8C 8E 90
DTA
namesSI
DI
H:> MASM mcr ?
H:> LINK mcr ?
H:> EXE2BIN mcr mcr.com? (转换为 COM文件)
COM文件的上机运行过程,
H:> mcr /m newdir? (建立 newdir目录)
程序装入内存后,命令行参数将存入 DTA,如下所示,
0 0 0 0 0 0 0 0 ?n e w d i r
4.8 多模块程序设计
在多模块程序设计中,会产生不同模块文件间的模块引
用和数据通信问题,解决上述问题需要用两条伪指令。
( 1)伪指令 PUBLIC
格式,PUBLIC 符号名 [,…]
将本模块中已定义的符号名(变量名、标号、过程名等)
定义为 全局符号, 允许其它模块引用 这些全局符号。
( 2)伪指令 EXTERN
格式,EXTERN 符号名:类型 [,…] ( 在 MASM 5.x 中为 EXTRN)
声明所列出的符号名是已在别的模块中定义了的 外部符号,
本模块可以直接引用 。
对于变量名:类型为 BYTE,WORD,DWORD,QWORD
对于标号或过程名:类型为 NEAR,FAR
例 4-38 ( P121) 从键盘输入一个十进制数,并将该数转换为
十六进制数后显示在屏幕上。
模块 1文件:
主过程功能,键盘 十进制数 二进制数 binum单元
输入 转换 存入
将上述功能分解成两部分,分配给两个模块完成:
convert.asm
模块 2文件:
子过程功能,binum单元 二进制数 十六进制数 显示取 转换
btoh.asm
学习要点,?不同文件中各模块的相互引用和数据通信
?将键盘输入按 十进制数转换的方法
设十进制数形式为,d2d1d0
d2?102+d1?101+d0 = ( ( ( ( 0?10 )+d2 )?10 )+d1 )?10+d0
模块 1要引用 模块 2中的过程名 btoh,因此必须将 btoh说明为
外部过程名,并且为 far类型,同时 模块 1中的 binum要被 模块 2引
用,所以 binum被定义为 全局符号名 。
模块 2要从 模块 1的 binum单元取得二进制数,因此必须将
binum说明为 外部符号名,并且为字类型 word,同时还须将 btoh
定义为 全局符号名 以供 模块 1引用。
3 0
0 3
0 ?10 3
3 0 ?10
3 0 ?10+3
5 3
3 5
3 ?10 5
5 3 ?10
5 3 ?10+5 binum
ax bx ax bx
设输入为,35
循环次数,1 2
= ( ( ( 0?10 )+3 )?10 )+5 binum
对两个模块分别进行汇编,然后连接运行:
255
FF
H:> MASM btoh
H:> LINK convert.obj + both.obj
H:> convert
H:> MASM convert
Input decimal number:
小 结
本章要求:
( 1) 熟练掌握 转移指令 (条件转移和无条件转移指令 )
及分支程序设计,循环指令 及循环程序设计,调用
指令 /返回指令 及子程序设计。 重点掌握 参数传递 方
法和 堆栈 在调用、返回、递归及参数传递中的作用。
( 2)理解 DOS系统功能调用 的概念,熟练运用 常用
的 DOS系统功能调用(如功能号 =1,2,9,0AH)。
( 3)掌握 COM格式 与 EXE格式 的汇编语言源程序编
写方法,了解 程序段前缀( PSP) 的结构。
( 4)理解多 模块程序设计 中应解决的 模块引用 和 数
据通信 问题,掌握多模块程序设计的方法。
认真复习,融会贯通
GOTO 第五章
寻找解决问题的
思路、方法、算法
描述算法
编写程序实现算法
设计测试数据
和预期计算结果
上机调试程序
第四章 汇编语言 程序设计的基本方法
程序设计的解题基本步骤如下:
顺序, 分支, 循环 程序和 子程
序 的设计是汇编语言程序设计的基
本内容。
在此基础上还要掌握汇编语言
程序设计的基本方法和技巧,包
括 递归子程序 设计,COM和 EXE
格式程序 的结构和特点,多模块
程序 设计方法等。
4.1 程序的几种基本结构
程序的基本结构有三种,顺序结构, 分支结构, 循环结构
顺序结构,按语句的书写顺序依次执行
分支结构,根据不同的条件判断结果,执行不同的程序段,
实现程序分支。
循环结构,由条件是否成立,确定是否重复执行某段程序。
4.2 顺序程序设计
例 4-1 (p 77) 查表求 2的整次幂( 2n,n=0,1,…, 14)
分析:
表地址 =array+2*n 就是存放 2n的单元地址
20 21 22 23 24 ……,214
字编号, 0 1 2 3 4
14
array
字节编号, 0 1 2 3 4 5 6 7 8 9 28
29
注意,2n在表中 (即数组 array中 )的存放规律,n正好是 2n在表中
的 存放字单元的顺序号 。 例, (array+2*3)= (array+6)= 23 =8
4.3 分支程序设计
程序的分支通过转移指令来实现,因此转移指令的操作
应该是能够改变程序执行顺序 。
4.3.1 转移指令
转移指令分为两类,无条件转移指令, 条件转移指令
1 ) 无条件转移指令
指令格式,JMP 标号 无条件 转移到 标号 指向的指令继续执行
无条件转移 段内转移段间转移 直接寻址间接寻址
(不影响任何标志位 )
(1) 段内转移(段内直接寻址)
?方法, 修改 CS:IP
段内转移 只需修改 IP实现转移, 使 IP 标号 所在行的指令
即,( IP) =标号所在行 指令的偏移地址
段内转移分为三种形式:
?段内直接短转移 格式,JMP SHORT 标号
操作,( IP) ← ( IP) +D( 8位偏移量 )
段内短转移
例,JMP SHORT NEXT
?
NEXT,MOV AL,4
MOV指令
D=30H
( IP)
B0
04
…
…
…
存储器
代码段
EB
30
0152H
0120H
0121H
0153H
0122H
?
JMP指令
下一条要执行的指令
段内短转移范围:
以 JMP指令为中心的 -128~ +127字节内
根据 JMP指令的操作
转移目的地址为:
(IP)+D=0122H+30H= 0152H (IP)
操作,( IP) ← ( IP) +D( 8位偏移量 )
?段内直接近转移(段内直接寻址)
格式,JMP NEAR PTR 标号
操作:( IP) ← ( IP) +D( 16位偏移量 ) -32768~ +32767字节范围
例 4-3 段内直接近转移举例
JMP NEAR PTR EXIT
?
EXIT,POP AX
两种转移指令都可以写成简化形式 ( 省略 SHORT和 NEAR),
JMP 标号
?汇编时如何确定标号的类型?
位移量 D= ? 8位 段内 短转移 指令> 8位 段内 直接近转移 指令
? 段内间接转移 ( 段内间接寻址 )
格式,JMP WORD PTR OPR 操作,( IP) ← ( OA)
OA是 由 OPR的寻址方式 确定的寄存器或存储单元 (字类型 ),
OA存的是 目的转移地址。
例, 段内间接转移
JMP BX ;( IP) ← ( BX) =转移地址
间接寻址转移指令的特点是指令中 没有直接给出标号
( 2) 段间转移
实现段间转移时, 转移目的地址由段地址和偏移地址构
成, 因此段间转移需要同时修改 CS和 IP。
?段间直接转移 ( 段间直接寻址 )
格式,JMP FAR PTR 标号
操作,( IP) ← 标号的 偏移地址
( CS) ← 标号所在段的 段地址
例 4-5 段间直接转移
CODEl SEGMENT
?
JMP FAR PTR NEW_SEG
?
CODEl ENDS
CODE2 SEGMENT
?
NEW_SEG,MOV AX,BX
?
CODE2 ENDS
JMP [BX] ;( IP) ← (( BX) ) =转移地址
34100H
32000H
XX
XX
…
…
…
CODE2
CODE1
操作码
21
00
32
NEW_SEG
00
…
? ?
EA
JMP指令 IP
CS
设 NEW_SEG标号的地址为 3200H,2100H
执行 JMP指令后, 程序流程控制
将从 CODEl代码段 CODE2代码段,
实现段间转移操作 。
?段间间接转移 ( 段间间接寻址 )
段间直接转移
格式,JMP DWORD PTR OPR
操作,( IP) ← ( OA)
( CS) ← ( OA+2)
OA是由 OPR的寻址方式所确定
的一个双字单元地址 。
( OA) =转移地址的 偏移地址
( OA+2) =转移地址的 段地址
( 段间间接转移见 P80,例 4-6)
2) 条件转移指令 ( 18条)
基本格式为,J×× 标号 ( ×× 代表一种转移条件 )
条件转移指令 根据检测标志位的状态来判断条件,若条件为
真,则转至标号处执行程序,否则顺序往下执行程序 。
操作:( IP) ← ( IP) +D ( 8位偏移量)
显然条件转移指令 转移范围与 段内短转移 相同
条件转移指令 分三类,
( 1) 一般 条件转移指令(见 P81 表 4-1)
用于某个检测标志位
( 2)用于 无符号数 的条件转移指令 (见 P81 表 4-2)
用于检测无符号数的比较
( 3)用于 带符号数 的条件转移指令 (见 P82 表 4-3)
用于检测带符号数的比较
先执行 影响标志位 的指令( 如算术运算、比较及位测试等
指令 ),再执行适当的条件转移指令检测相应的标志位,根据
条件实现转移。
例,利用条件转移指令 构成循环
?
MOV CX,100 (循环计数)
AGAIN:
?
DEC CX
JNZ AGAIN
?
4.3.2 分支程序设计方法
一般分为两类:
? 条件转移指令 —— 实现程序两路分支
?无条件转移指令 +跳转表 —— 实现程序多路分支
1) 由条件转移指令实现程序分支
例 4-9(p83),数据块传送。如果源数据区与目的数据区位置
没有重叠部分,则正向传送数据块,否则反向传送数据块。
分析:
stg1 stg2
299
存储区无重叠
0
0
stg2
299
存储区有重叠
stg1
正向传送
反向传送
?如何判断存储区是否重叠?
存储区 无重叠的判断条件,
源区结束地址 (stg1+源区长度 -1) < 目的区起始地址 (stg2)
程序及流程图见教材 p83 例 4-9
2) 用无条件转移指令 +跳转表实现程序分支
跳转表便于实现多路分支。
设有若干段分支程序,将每段 分支程序的入口地址 (也称
跳转地址)组成一个连续存放在内存中的表,称为 跳转表 。
若有 n个产品,将这 n个产品的加工处理程序(分支程序)入
口地址(分别为 BR0,BR1,…, BRn-1)组成跳转表,可就利
用跳转表取得 BR0,BR1,…, BRn-1,转移到相应的 分支程序
去处理产品。
由 表地址 就可取得分支程序地址 BRn,再将 BRn送 IP,实现转移。
例,根据学生成绩评定等级,90~ 100分的等级为, A”; 80~ 89
分的等级为, B”; 70~ 79分的等级为, C”,60~ 69分的等级为
,D”,<60分的等级为, E” 。
分析,0~ 59 60~ 69 70~ 79 80~ 89 90 ~ 100
0 1 2 3 4
tab dw br0, br1, br2, br3, br4 (跳转表)
E D C B A
(程序见 P84 例 4-10) next
设 n=2,存放 BR2的表地址为:
表地址 =表首址 + BR2在表中的 位置序号 ? 表元素 占用的 字节数
设( BX) =存放 BR2的单元 表地址
返回
JMP [BX] ; (( BX) ) ( IP) =BR2,实现转移
?对于 (b)中的跳转表,怎样使用 JMP指令实现转移? JMP BX
4.4 循环程序设计
根据条件重复执行一段指令就构成了循环程序结构 。
MOV AX,0 ;累加求和寄存器清 0
MOV BX,OFFSET ARRAY ;数组始址送 BX中
MOV CX,50 ;循环控制计数初始化
AGAIN, ADD AX,[BX]
ADD BX,2 ;数组地址增 2,BX指向下一个元素
DEC CX ;循环控制计数减 1
JNZ AGAIN ;计数不为 0时继续循环
MOV S,AX ;计数为 0时循环结束,和存于 S中
保证循环正常执行和结束的条件:
?正确地 初始化 循环控制计数和初始条件
?正确地 检测 循环条件
?必须保证能 达到循环结束条件 (例如在循环体内有 修改循环
控制值 的指令 )
例,将 ARRAY数组中的 50个数求和,并将和存入字变量 S中 。
指令系统提供了专门用于循环结构的 循环控制指令,更加
简便的地实现循环结构。
4.4.1 循环控制指令
循环控制指令的转移范围为,-128~ +127字节
( 2)等于继续循环指令
格式,LOOPZ/LOOPE 标号
( 3)不等于继续循环指令
格式,LOOPNZ/LOOPNE 标号
LOOP AGAIN
(段内短转移)
格式,LOOP 标号
操作,如图所示
( 1) LOOP指令
(CX)≠ 0?
执行循环体
(CX) (CX) -1
是
否
退出循环
(CX) ≠ 0 且 ZF =1?) ≠ 0 且 ZF=0?
在前面的求和例子中,可用一条循环指令代替两条指令:
DEC CX
JNZ AGAIN
MOV CX,80
MOV SI,-1
MOV AL,?$?
AGAIN,INC SI
CMP AL,STRS[SI]
LOOPNE AGAIN
MOV LEN,SI
4.4.2 循环程序设计方法
控制循环的方法可分为两类:
计数循环 —— 用于循环次数已知的情况
条件循环 —— 用于循环次数不确定的情况
设, STRS DB ?string$ ?
例, 求 存放在 STRS存储区中的 字符串长度。
例 4-16( P90), 将字变量 num中的数按 16位 二进制数位 分离
开, 逐位 转换为 ASCII码, 并从最高有效位开始依次逐位存入
以 binbuf为起始地址的 16个字节单元中 。
设,( num) = 4230H
要求将 4230H= 0 1 0 0 0 0 1 0 0 0 1 1 0 0 0 0 B 逐位分离开
并转换为:
30 31 30 30 30 30 31 30 30 30 31 31 30 30 30 30
先将 ( num) ->( DX),再讨论如何分离开各二进制位并转
换为 ASCII码, 分析如下,
根据上述分析, 将 ( DX) 中的二进制数依次 循环左移 1位,
可以使得 各个数位的分离 ( AND) 和 ASCII码转换 ( ADD) 操
作完全相同, 便于组织循环, 并且 循环次数已知 ( 16次 ) 。
例 4-17( P91), 将 十进制数 ( number) 转换为八进制数
方法, ( number) /8 保留余数, 除法操作 循环次数事先未知,
仅当商为 0时, 循环结束 。 此例为条件循环
此例为计数循环
next
( DX) = 0 1 0 0 0 0 1 0 0 0 1 1 0 0 0 0
1 0 0 0 0 1 0 0 0 1 1 0 0 0 0 0 ( ROL DX,1 )
? 0 0 0 0 0 0 0 1 ( AND?, 1)
0 0 0 0 0 0 0 0
+ 0 0 1 1 0 0 0 0 ( ADD?, 30H)
0 0 1 1 0 0 0 0
30
0 0 0 0 1 0 0 0 1 1 0 0 0 0 0 1 ( ROL DX,1)
? 0 0 0 0 0 0 0 1 ( AND)
0 0 0 0 0 0 0 1
+ 0 0 1 1 0 0 0 0 ( ADD)
0 0 1 1 0 0 0 1
31
0 1 0 0 0 0 1 0 0 0 1 1 0 0 0 0 ( ROL DX,1)
? 0 0 0 0 0 0 0 1 ( AND)
0 0 0 0 0 0 0 0
+ 0 0 1 1 0 0 0 0 ( ADD)
0 0 1 1 0 0 0 0
… …
30 31 30 30 30 30 31 30 30 30 31 31 30 30 30
1.
2.
16.
binbuf 返回30
例 4-18 统计 AX中的二进制数含, 1”的总个数, 程序段如下:
MOV CX,0
AGAIN,AND AX,AX ; ( AX) =0?
JZ QUIT ; 是, 退出循环
SAL AX,1 ; 否,( AX) 的最高位移入 CF位
JNC NEXT ; CF≠1时, 转向 NEXT再次循环
INC CX ; CF=1,计数器 (CX)←(CX) +1
NEXT,JMP AGAIN ; 再次循环
QUIT, ?
上述程序为 当型循环 程序结构,先判断条件,后执行循环 。
课堂练习,
采用循环结构完成自然数 1到 100的求和运算。
next
当型循环与直到型循环,
返回
参考答案 2:
.model small
.code
mov ax,@data
mov ds,ax
xor ax,ax
mov cx,100
again,add ax,cx
loop again
mov ax,4c00h
int 21h
end
参考答案 1:
.model small
.data
num dw 1
.code
mov ax,@data
mov ds,ax
mov ax,0
mov cx,100
again,add ax,num
inc num
loop again
mov ax,4c00h
int 21h
end
( 下述例子可用 5.X 或 6.X 版汇编程序汇编 )
思考题:显示下面数组中的数据:
ARRAY DB 1,3,7,8,5,4,6,8
.model small
.data
ARRAY DB 1,3,7,8,5,4,6,8
LEN EQU $- ARRAY
思考题 参考答案:
.code
mov ax,@data
mov ds,ax
lea bx,ARRAY
mov cx,LEN
again,mov dl,[bx]
add dl,30h
mov ah,2
int 21h
inc bx
loop again
mov ax,4c00h
int 21h
end
4.4.3 多重循环程序设计
例, 分析下述程序的功能,
mov bl,20
delay,mov cx,9801h ;外循环开始
idle,loop idle ;内循环体
dec bl
jnz delay ;外循环结束
设计 多重循环程序 时注意下面两点:
消耗时间 延时功能
(2) 转移指令只能从循环结构
内转到循环结构外
(1) 内层循环必须完全
包含在外层循环内
? ? ? ?
多重循环程序能够解决更复杂的实际问题。
4.5 子程序设计
子程序结构是模块化程序设计的重要基础,其 调用 和 返回 分
别由 CALL和 RET指令完成,子程序设计方法包括 子程序定义,
参数传递,子程序的 嵌套调用 和 递归调用 。
4.5.1 调用与返回指令 ( 不影响标志位)
1) 调用指令
CALL指令 的寻址方式类似 JMP指令,也分为四种调用方式。
( 1)段内直接调用
格式,CALL dst
操作,( SP) ← ( SP) -2
(( SP) +1,( SP) )← ( IP) (返回地址 )
( IP) ← ( IP) +D (子程序入口地址 )
保护断点地址
实现转移
类似段内直接近转移操作
?
CALL max ;调用子程序 max
mov ax,bx ;断点指令
?
max proc near ;子程序入口
?
ret
max endp
D
(IP)->
(IP)+D ->(IP)
例 4-23 段内直接调用 子程序 max
转移到 max?
?保护断点 (mov指令 )地址
(IP)入栈
CALL指令操作,
2)段内间接调用
格式,CALL dst
操作,( SP) ← ( SP) -2,(( SP) +1,( SP) )← ( IP)
( IP) ← ( OA)
(OA是存储器单元地址或寄存器,(OA)=子程序入口的偏移地址 )
例 4-24 段内间接调用 子程序 max
CALL BX ;保护断点,IP← ( BX) = max入口的偏移地址
( 3)段间直接调用
格式,CALL dst
操作,( CS) 入栈
( IP) 入栈
( IP) ← 子程序入口的偏移地址
( CS) ← 子程序入口的段地址
保护断点
例 4-25 段间直接调用 子程序 max
CALL FAR PTR max
操作,?保护断点,CS,IP当前值 (断点地址 )入栈
?实现转移,max入口的 段地址和偏移地址送 CS,IP
4) 段间间接调用
格式,CALL dst 操作, (CS),(IP) 入栈,保护断点
( IP) ← ( OA), ( CS) ← ( OA+2)
OA是双字单元地址, 存放子程序入口地址 。
例 4-26 段间间接调用子程序 max
CALL DWORD PTR [BX]
操作,?保护断点,CS,IP当前值 (断点地址 )入栈
?实现转移,( (IP)← ( (BX)),( CS) ← ( (BX)+2)
BX所指双字单元 ( OA) =max入口的 段地址 和 偏移地址
2) 返回指令 ( 不影响标志位)
子程序执行的最后一条指令必须是 RET指令, 返回到 CALL
指令后的断点处继续执行。
( 1)段内返回
格式,RET
操作,( IP) ← (( SP) +1,( SP))
( SP) ← ( SP) +2
将栈顶的断点地址出栈 IP,返回到断点处继续执行 。
( 2)段内带立即数返回
格式,RET 表达式 ( D )
操作,( IP) ← (( SP) +1,( SP))
( SP) ← ( SP) +2
( SP) ← ( SP) +D ( 16位的偏移量)
例,带立即数返回 举例
push ax ;参数 1入栈
push bx ;参数 2入栈
CALL max ;调用子程序 max
mov ax,bx ;断点指令
?
max proc near ;子程序入口
?
ret
max endp
参数 1
参数 2
IP
SP
SP
SP
4
SP
SP
(参数个数 ?2)
( 3)段间返回
格式,RET
( 4)段间带立即数返回
格式,RET 表达式
段间返回 指令操作与 段内返回 指令指令类似,差别只是:
段间返回指令从堆栈中 连续弹出两个字
第一个字(断点的偏移地址)出栈送 IP
第二个字(断点的段地址)出栈送 CS
4.5.2 子程序的编写方法
子程序的优点:简化程序书写,减小代码长度,并且便于
子程序功能的共享和大型程序的模块化组织。
子程序的类型属性, FAR 和 NEAR
( 1) NEAR型, 调用程序和子程序在同一代码段中
( 2) FAR型, 调用程序和子程序不在同一代码段中 P99-P100
( 2) 编写子程序的一般要求
( 1)保护寄存器
( 2)正确使用堆栈
必须成对地执行 PUSH和 POP指令, 使 RET指令能正确地
弹出断点返回地址。
( 3)编写适当的子程序内部文档
使子程序可读性好、易理解和便于使用
sub1 proc near ;子程序入口
PUSH AX
PUSH BX
PUSH CX
?
POP CX
POP BX
POP AX
ret
sub1 endp
出栈与入栈顺序应当相反
next
?
CALL sub1 ;调用子程序 max
mov ax,bx ;断点指令
?
sub1 proc near ;子程序入口
push ax ;数据 1入栈
push bx ;数据 2入栈
?
pop bx ;数据 1出栈
pop ax ;数据 2出栈
ret
sub1 endp
ax
bx
IP
SP
SP
SP
SP
SP
返回
例:子程序内 必须 成对地 执行 PUSH和 POP指令,才能正常
返回。
IP
( 4)参数传递
参数可以增加子程序的灵活性和通用性。调用程序传送给子
程序的参数称为入口参数,子程序返回给调用程序的结果称为
出口参数。
参数传递一般有如下三种方法:
?用寄存器传递参数,将参数送入约定的寄存器中,用于参数
较少的情况。
?用参数表传递参数,调用程序将参数组成一个参数表传送
给子程序,用于大量参数的传送。
?用堆栈传递参数,调用程序将参数压入堆栈,子程序从堆栈
中取得参数。用于参数多、子程序嵌套调用和递归调用 。
本节分别讨论 用 寄存器传递参数 和用 堆栈传递参数 的方法例子。
例 4-29 ( P104) 在一个带符号数的数组中,选出绝对值最大的
值并确定该值存放单元的位置序号。
选最大值方法的分析:
120 430 -44 …
0 1 2
AX 120
DL 0
BX 430
DH 1
430
1 2
-44
学习要点, ?用寄存器传递参数的方法
?选最大值的方法
例 4-31 程序功能与例 4-29相同, 不同的只是本例利用堆栈
将入口参数传送给子程序 。
学习要点,?用堆栈传递参数的方法
?bp寄存器的使用
?带立即数返回指令的使用( ret n)
array
程序分析如下:
程序执行过程中堆栈的变
化情况如图所示。
元素个数
数组首址
IP
bp
ax
bx
cx
dx
si
di
bp
SP
SP
SP
最大值
位置序号
SP
SP
4.5.3 子程序的嵌套调用与递归调用
( 1) 子程序的嵌套调用
A->B->C
( 2) 子程序的递归调用
A->B->A
A->A
间接递归调用
直接递归调用
子程序的递归调用需要由堆栈传递
参数,每次递归调用时将入口 /出口参数、
寄存器内容以及所有的中间结果保存在
堆栈中 。
优点,简洁、易读 缺点,空间开销大
-> ( si )
-> ( cx )
<- ( ax )
<- (dx)
例 4-32 用递归的方法将一个字符数组中的字符串反序输出,
字符串以 ?$?为结束符。 设 strs db ?ab$ ?
ax
bx
IP0
SP
程序执行过程中堆栈的变化如下,
dx
strs
bp0SPbp
1
bp1+0
+2
+4
+6
+8
+10( a )
SP
bp2
( b )
ax
bx
IP1
dx
strs+1
bp1 bp2+0
+2
+4
+6
+8
+10
SP
bp3
( $ )
ax
bx
IP2
dx
strs+2
bp2 bp3+0
+2
+4
+6
+8
+10
//////////
//////////
//////////
//////////
//////////
//////////
//////////
//////////
//////////
//////////
//////////
//////////
//////////
//////////
//////////
//////////
//////////
//////////
$反向输出, b a
( 1)每次递归调用时,正向依次压入各字符的单元地址。
( 2)逐层返回时,反向逐次弹出各字符的单元地址并输出其
中的字符。
参考答案, ( 用寄存器传递参数 )
.model small
.data
ARRAY dw 1,3,7,8,5,4,6,8
LEN equ ($-ARRAY)/2
sum dw?
.code
.startup
mov si,offset array
mov cx,LEN
call get_sum
mov sum,dx
.exit 0
课堂练习, 定义子程序 get_sum 对下面数组中的数据求和,
ARRAY DW 1,3,7,8,5,4,6,8
入口参数, (si)=数组首址 (cx)=元素个数 出口参数, (dx)=数组和
子程序
get_sum proc
push ax
mov ax,0
again,add ax,[si]
inc si
inc si
loop again
mov dx,ax
pop ax
ret
get_sum endp
end
4.6 DOS系统功能调用
DOS系统功能调用 是 DOS为用户提供的常用功能子程序,
功能包括:
( 1)基本输入 /输出
( 2)磁盘读写管理
( 3)文件与内存管理
每个功能子程序有一个编号,可以按功能编号在汇编浯言
程序中直接调用 (p325)。
1 ) 系统功能调用方法
调用 DOS系统功能调用的步骤:
( 1) 功能号 AH寄存器
( 2) 入口参数 指定寄存器;
( 3) 执行 int 21h 指令, 调用功能子程序完成指定的操作
( 4) 从指定寄存器取得 出口参数
2 ) DOS系统功能调用 举例
功能:从键盘输入字符串并存放到输入缓冲区中
功能号,0AH
入口参数,( ds) =输入缓冲区所在段 段地址
( dx) =输入缓冲区的起始 偏移地址
例 4-35 定义缓冲区 buf,存放调用 0AH功能 从键盘输入的字符串:
buf db 20 ;存放输入字符串的缓冲区长度
db? ;由系统填入实际输入字符串长度
db 20 dup (?) ;存放输入字符串的缓冲区
调用 DOS系统功能调用 0AH,
mov ah,0AH
lea dx,buf
int 21h
如果键盘输入字符串为,ABC?
buf + 0 1 2 3 4 5 6
14 3 41 42 43 0D
分析例 4-36( p114)
4.7 EXE文件与 COM文件
用汇编语言可以编写两种格式的源程序:
格式一,经汇编、连接后产生,EXE可执行文件
格式二,经汇编、连接、转换成,COM可执行文件。
( 1) EXE文件
EXE程序装入内存时的
映像如图 4.25所示。
程序段前缀 PSP的结构:
( 256字节) 表 4-4( p116)
next
程序段前缀 PSP的 80H~FFH:隐含的数据传输区( DTA)
DTA可用于传递命令行参数,其中:
H:> COPY oldfile newfile?
( 80H) = 命令行参数串长度 (从命令名后第一个空格字符算起)
81H单元开始存放 命令行参数串
例如:拷贝文件命令
返回
10 o l d f i l e n e w f i l e
80 81 82 84 86 88 8A 8C 8E 90
DTA
( 2) COM文件
COM程序装入内存时的映像如图 4.25所示。
( 1)代码段、数据
段及堆栈段在同一段,
?64KB。
( 4)所有的子过程应定义为 NEAR类型
( 5)直接用 INT 20H退出程序返回 DOS
( 2)第一条指令的偏
移地址固定为 100H,
即有( IP) =100H。
( 3) SP指向该物理
段末尾,故 不用定义
堆栈段 。
COM源程序的格式:
code segment ;只需定义代码段
assume cs,code,ds,code,es,code
org 100h ;第一条可执行命令必须从 100H开始存放
start,jmp begin
? ;数据定义部分
begin,;程序主体部分
?
int 20h ;用 int 20h指令返回 DOS
code ends
end start
例 4-37( P118) 编写程序 mcr.asm,根据在命令行给出的参数
和目录名,利用 DOS功能调用完成 建立目录, 删除目录 或 改变
工作目录 的功能。
命令格式为, mcr 参数 目录名
参数为,/m— 建立目录 /r— 刪除目录 /c— 改变工作目录
若命令行为,mcr /m newdir
程序执行后将建立一个新目录 newdir。
程序中用到的 DOS功能调用:
( dx) = 目录名串首址
( ah) = 39h (建目录)
3ah(删除目录)
3bh(改变工作目录)
返回参数,
CF=0 操作成功
CF=1 操作失败,(ax)=错误号
学习要点, ?编写 COM程序的方法
?命令行参数的获取方法
?DOS功能调用
H:>mcr /m newdir?
0A / m n e w d i r
80 81 82 84 85 86 88 8A 8C 8E 90
DTA
namesSI
DI
H:> MASM mcr ?
H:> LINK mcr ?
H:> EXE2BIN mcr mcr.com? (转换为 COM文件)
COM文件的上机运行过程,
H:> mcr /m newdir? (建立 newdir目录)
程序装入内存后,命令行参数将存入 DTA,如下所示,
0 0 0 0 0 0 0 0 ?n e w d i r
4.8 多模块程序设计
在多模块程序设计中,会产生不同模块文件间的模块引
用和数据通信问题,解决上述问题需要用两条伪指令。
( 1)伪指令 PUBLIC
格式,PUBLIC 符号名 [,…]
将本模块中已定义的符号名(变量名、标号、过程名等)
定义为 全局符号, 允许其它模块引用 这些全局符号。
( 2)伪指令 EXTERN
格式,EXTERN 符号名:类型 [,…] ( 在 MASM 5.x 中为 EXTRN)
声明所列出的符号名是已在别的模块中定义了的 外部符号,
本模块可以直接引用 。
对于变量名:类型为 BYTE,WORD,DWORD,QWORD
对于标号或过程名:类型为 NEAR,FAR
例 4-38 ( P121) 从键盘输入一个十进制数,并将该数转换为
十六进制数后显示在屏幕上。
模块 1文件:
主过程功能,键盘 十进制数 二进制数 binum单元
输入 转换 存入
将上述功能分解成两部分,分配给两个模块完成:
convert.asm
模块 2文件:
子过程功能,binum单元 二进制数 十六进制数 显示取 转换
btoh.asm
学习要点,?不同文件中各模块的相互引用和数据通信
?将键盘输入按 十进制数转换的方法
设十进制数形式为,d2d1d0
d2?102+d1?101+d0 = ( ( ( ( 0?10 )+d2 )?10 )+d1 )?10+d0
模块 1要引用 模块 2中的过程名 btoh,因此必须将 btoh说明为
外部过程名,并且为 far类型,同时 模块 1中的 binum要被 模块 2引
用,所以 binum被定义为 全局符号名 。
模块 2要从 模块 1的 binum单元取得二进制数,因此必须将
binum说明为 外部符号名,并且为字类型 word,同时还须将 btoh
定义为 全局符号名 以供 模块 1引用。
3 0
0 3
0 ?10 3
3 0 ?10
3 0 ?10+3
5 3
3 5
3 ?10 5
5 3 ?10
5 3 ?10+5 binum
ax bx ax bx
设输入为,35
循环次数,1 2
= ( ( ( 0?10 )+3 )?10 )+5 binum
对两个模块分别进行汇编,然后连接运行:
255
FF
H:> MASM btoh
H:> LINK convert.obj + both.obj
H:> convert
H:> MASM convert
Input decimal number:
小 结
本章要求:
( 1) 熟练掌握 转移指令 (条件转移和无条件转移指令 )
及分支程序设计,循环指令 及循环程序设计,调用
指令 /返回指令 及子程序设计。 重点掌握 参数传递 方
法和 堆栈 在调用、返回、递归及参数传递中的作用。
( 2)理解 DOS系统功能调用 的概念,熟练运用 常用
的 DOS系统功能调用(如功能号 =1,2,9,0AH)。
( 3)掌握 COM格式 与 EXE格式 的汇编语言源程序编
写方法,了解 程序段前缀( PSP) 的结构。
( 4)理解多 模块程序设计 中应解决的 模块引用 和 数
据通信 问题,掌握多模块程序设计的方法。
认真复习,融会贯通
GOTO 第五章