第 4 章教学重点综合应用第 2章硬指令和第
3章伪指令,第 4章从程序结构角度展开程序设计,重点掌握:
分支结构程序设计
循环结构程序设计
子程序结构程序设计第 4章 4.1 顺序程序设计
顺序程序完全按指令书写的前后顺序执行每一条指令,是最基本,
最常见的程序结构例 4.1 计算例 4.2 移位例题 代码转换例 4.1
.model small
.stack 256
.data
X dw 5
Y dw 6
Z dw 7
W dw?
.code
.startup
mov ax,X
add ax,Y
adc ax,Z
mov W,ax
.exit 0
end
例 4.2- 1/2.data
qvar dq 1234567887654321h
.code
mov al,byte ptr qvar[6]
mov byte ptr qvar[7],al
mov al,byte ptr qvar[5]
mov byte ptr qvar[6],al
mov al,byte ptr qvar[4]
mov byte ptr qvar[5],al
mov al,byte ptr qvar[3]
mov byte ptr qvar[4],al
图示例 4.2- 2/2mov al,byte ptr qvar[2]
mov byte ptr qvar[3],al
mov al,byte ptr qvar[1]
mov byte ptr qvar[2],al
mov al,byte ptr qvar[0]
mov byte ptr qvar[1],al
mov byte ptr qvar[0],0
12 34 56 78 87 65 43 21h
34 56 78 87 65 43 21 00h移位后图示例题 代码转换- 1/2;查表法,实现一位 16进制数转换为 ASCII码显示
.model small
.stack 256
.data
ASCII db 30h,31h,32h,33h,34h,35h
db 36h,37h,38h,39h ;0~ 9的 ASCII码
db 41h,42h,43h,44h,45h,46h;A~ F的 ASCII码
hex db 0bh;任意设定了一个待转换的一位 16进制数例题 代码转换- 2/2
.code
.startup
mov bx,offset ASCII ;BX指向 ASCII码表
mov al,hex;AL取得一位 16进制数,正是 ASCII码表中位移
and al,0fh ;只有低 4位是有效的,高 4位清
0
xlat ;换码,AL←DS,[BX+ AL]
mov dl,al ;入口参数,DL←AL
mov ah,2 ;02号 DOS功能调用
int 21h ;显示一个 ASCII码字符
.exit 0
end
第 4章 4.2 分支程序设计
分支程序根据条件是真或假决定执行与否
判断的条件是各种指令,如 CMP,TEST等执行后形成的状态标志
转移指令 Jcc和 JMP可以实现分支控制;还可以采用 MASM 6.x提供的条件控制伪指令实现单分支:例 2.41等双分支:例 4.3等多分支:例 4.4等单分支程序设计
条件成立跳转,
否则顺序执行分支语句体;注意选择正确的条件转移指令和转移目标地址第 4章例 2.41 求绝对值;计算 X- Y的绝对值
mov ax,X
sub ax,Y
jns nonneg ;条件满足 ( X-Y≥ 0)?
neg ax ;条件不满足,求补
nonneg,mov result,ax ;条件满足;计算 AX的绝对值
mov ax,0
jge nonneg ;条件满足 ( AX≥ 0)?
neg ax ;条件不满足,求补
nonneg,mov result,ax ;条件满足例题 无符号数除以 2;将 AX中存放的无符号数除以 2,如果是奇数,则加 1后除以 2
test ax,01h ;测试 AX最低位
jz even ;最低位为 0,AX为偶数
add ax,1;最低位为 1,AX为奇数,需要加 1
even,rcr ax,1 ;AX←AX ÷ 2;如果采用 SHR指令,在不能处理 AX= FFFFH
的特殊情况双分支程序设计条件成立跳转执行第
2个分支语句体,否则顺序执行第 1个分支语句体 。 注意第 1
个分支体后一定要有一个 JMP指令跳到第
2个分支体后第 4章例题 显示 BX最高位
shl bx,1 ;BX最高位移入 CF
jc one ;CF= 1,即最高位为 1,转移
mov dl,30h;CF= 0,即最高位为 0,DL← ’0’
jmp two ;一定要跳过另一个分支体
one,mov dl,31h;DL← ’1’
two,mov ah,2
int 21h;显示编写分支程序,需留心分支的开始和结束例 4.3 判断有无实根- 1/2
.startup
mov al,_b
imul al
mov bx,ax ;BX中为 b2
mov al,_a
imul _c
mov cx,4
imul cx ;AX中为 4ac( DX无有效数据 )
例 4.3 判断有无实根- 2/2
cmp bx,ax ;比较二者大小
jge yes ;条件满足?
mov tag,0;第一分支体:条件不满足,tag← 0
jmp done ;跳过第二个分支体
yes,mov tag,1;第二分支体:条件满足,tag← 1
done,.exit 0;寄存器 AL中是字母 Y或 y,在令 AH= 0;否则令 AH=- 1
cmp al,’Y’ ;AL是大写 Y否?
jz next ;是,转移
cmp al,’y’ ;AL是小写 y否?
jz next ;是,转移
mov ah,-1 ;不是 Y或 y,在 AH=- 1,结束
jmp done ;一定要跳过另一个分支体
next,mov ah,0 ;是 Y或 y,在 AH= 0,结束
done,...
例题 单分支和双分支多分支程序设计
多个条件对应各自的分支语句体,哪个条件成立就转入相应分支体执行 。 多分支可以化解为双分支或单分支结构的组合,例如:
or ah,ah ;等效于 cmp ah,0
jz function0 ;ah= 0,转向 function0
dec ah ;等效于 cmp ah,1
jz function1 ;ah= 1,转向 function1
dec ah ;等效于 cmp ah,2
jz function2 ;ah= 2,转向 function2
第 4章图示第 4章 地址表形成多分支
需要在数据段事先安排一个按顺序排列的转移地址表
输入的数字作为偏移量 。 因为只有 2个字节 16位偏移地址,所以偏移量需要乘 2
关键是要理解间接寻址方式 JMP指令地址表 分支 1地址 分支 2地址,..
Table db disp1,disp2,disp3,disp4,...
.data
msg db 'Input number(1~8):',0dh,0ah,'$'
msg1 db 'Chapter 1,,..',0dh,0ah,'$'
msg2 db 'Chapter 2,,..',0dh,0ah,'$‘
...
msg8 db 'Chapter 8,,.,',0dh,0ah,'$'
table dw disp1,disp2,disp3,disp4
dw disp5,disp6,disp7,disp8;取得各个标号的偏移地址例 4.4 数据段- 1/3
此处等同于 offset disp1
start1,mov dx,offset msg;提示输入数字
mov ah,9
int 21h
mov ah,1 ;等待按键
int 21h
cmp al,'1' ;数字 < 1?
jb start1
cmp al,'8' ;数字 > 8?
ja start1
and ax,000fh ;将 ASCII码转换成数字例 4.4 代码段- 2/3
dec ax
shl ax,1 ;等效于 add ax,ax
mov bx,ax
jmp table[bx];( 段内 ) 间接转移,IP←[table+bx]
start2,mov ah,9
int 21h
.exit 0
disp1,mov dx,offset msg1 ;处理程序 1
jmp start2
...
例 4.4 代码段- 3/3
可以改为 call table[bx]
对应修改为 ret
第 4章 4.3 循环程序设计
循环结构一般是根据某一条件判断为真或假来确定是否重复执行循环体
循环指令和转移指令可以实现循环控制;还可以采用 MASM 6.x提供的循环控制伪指令实现循环指令 LOOPE:例 4.6
转移指令:例 4.7
多重循环:例 4.8等循环指令 LOOP:例 4.5等第 4章 循环结构结束初始化 循环的初始状态循环体循环的工作部分及修改部分计数控制循环条件控制循环修改部分控制条件Y
N
.model small
.stack 256
.data
sum dw?
.code
.startup
xor ax,ax ;被加数 AX清 0
mov cx,100
again,add ax,cx;从 100,99,...,2,1倒序累加
loop again
mov sum,ax ;将累加和送入指定单元
.exit 0
end
例 4.5 求和计数控制循环循环次数固定;用二进制显示从键盘输入的一个字符的 ASCII码
mov ah,1 ;从键盘输入一个字符
int 21h
mov bl,al ;BL←AL =字符的 ASCII码;DOS功能会改变 AL内容,故字符 ASCII码存入 BL
mov ah,2
mov dl,':' ;显示一个分号,用于分隔
int 21h
习题 4.21- 1/2
mov cx,8 ;CX← 8( 循环次数 )
again,shl bl,1 ;左移进 CF,从高位开始显示
mov dl,0 ;MOV指令不改变 CF
adc dl,30h ;DL← 0+ 30H+ CF;CF若是 0,则 DL←' 0';若是 1,则 DL←' 1'
mov ah,2
int 21h ;显示
loop again;CX减 1,如果 CX未减至 0,则循环习题 4.21- 2/2
计数控制循环循环次数固定
.startup
mov ax,wordX ;测试目标送 AX
mov cx,16 ;循环计数器置初值
mov dl,-1 ;计位器置初值
again,inc dl
test ax,1
ror ax,1 ;循环指令不影响 ZF
loope again;CX≠ 0且 ZF=1( 测试位为 0),继续循环
je notfound
mov byteY,dl
jmp done
notfound,mov byteY,-1 ;ZF=1,16个位均为 0
done,.exit 0
例 4.6
计数控制循环最大循环次数固定,满足条件退出
mov bx,offset string
again,mov al,[bx] ;取一个字符
or al,al ;是否为结尾符 0
jz done ;是,退出循环
cmp al,'A' ;是否为大写 A~ Z
jb next
cmp al,'Z'
ja next
or al,20h;是,转换为小写字母 ( 使 D5=1)
mov [bx],al ;仍保存在原位置
next,inc bx
jmp again ;继续循环
done,.exit 0
例 4.7 大小写条件控制循环利用标志退出大小写字母仅 D5位 不同第 4章 冒泡法
“冒泡法,是一种排序算法,不是最优的算法,但它易于理解和实现
冒泡法从第一个元素开始,依次对相邻的两个元素进行比较,使前一个元素不大于后一个元素;将所有元素比较完之后,最大的元素排到了最后;然后,除掉最后一个元素之外的元素依上述方法再进行比较,得到次大的元素排在后面;如此重复,直至完成就实现元素从小到大的排序
这需要一个双重循环程序结构图示
mov cx,count ;CX← 数组元素个数
dec cx ;元素个数减 1为外循环次数
outlp:mov dx,cx ;DX← 内循环次数
mov bx,offset array
inlp,mov al,[bx] ;取前一个元素
cmp al,[bx+1] ;与后一个元素比较
jna next;前一个不大于后一个元素,则不进行交换
xchg al,[bx+1];否则,进行交换
mov [bx],al
next,inc bx ;下一对元素
dec dx
jnz inlp ;内循环尾
loop outlp ;外循环尾例 4.8
计数控制双重循环;现有一个以 $结尾的字符串,要求剔除其中的空格
.data
string db ’Let us have a try !’,’$’
.code
.startup
mov dx,offset string
mov ah,9
int 21h ;显示原字符串
mov al,’ ’;AL← 空格 ( ASCII码为 20H)
mov di,offset string
例题 剔除空格- 1/3
outlp,cmp byte ptr [di],’$’;外循环,先判断后循环
jz done ;为 $结束
cmp al,[di] ;检测是否是空格
jnz next ;不是空格继续循环
mov si,di ;是空格,进入剔除空格分支;该分支是循环程序段
inlp,inc si
mov ah,[si] ;前移一个位置
mov [si-1],ah
cmp byte ptr [si],’$’;内循环,先循环后判断
jnz inlp
例题 剔除空格- 2/3 条件控制双重循环
next,inc di ;继续对后续字符进行处理
jmp outlp
done,mov dx,offset string
mov ah,9
int 21h ;显示处理后字符串
.exit 0 ;结束
end
例题 剔除空格- 3/3
第 4章 4.4 子程序设计
把功能相对独立的程序段单独编写和调试,作为一个相对独立的模块供程序使用,就形成子程序
子程序可以实现源程序的模块化,可简化源程序结构,可以提高编程效率子程序设计要利用过程定义伪指令参数传递是子程序设计的重点和难点子程序可以嵌套;
一定条件下,还可以递归和重入
4.4.1 程序定义伪指令过程名 proc [near|far]
...
过程名 endp
过程名 ( 子程序名 ) 为符合语法的标识符
NEAR属性 ( 段内近调用 ) 的过程只能被相同代码段的其他程序调用
FAR属性 ( 段间远调用 ) 的过程可以被相同或不同代码段的程序调用
对简化段定义格式,在微型,小型和紧凑存储模式下,过程的缺省属性为 near;在中型,大型和巨型存储模式下,过程的缺省属性为 far
对完整段定义格式,过程的缺省属性为 near
用户可以在过程定义时用 near或 far改变缺省属性子程序的常见格式
subname proc ;具有缺省属性的 subname过程
push ax ;保护寄存器,顺序压入堆栈
push bx ;ax/bx/cx仅是示例
push cx
… ;过程体
pop cx ;恢复寄存器,逆序弹出堆栈
pop bx
pop ax
ret ;过程返回
subname endp ;过程结束第 4章;子程序功能:实现光标回车换行
dpcrlf proc ;过程开始
push ax ;保护寄存器 AX和 DX
push dx
mov dl,0dh ;显示回车
mov ah,2
int 21h
mov dl,0ah ;显示换行
mov ah,2
int 21h
pop dx ;恢复寄存器 DX和 AX
pop ax
ret ;子程序返回
dpcrlf endp ;过程结束例题 无参数传递的子程序
ALdisp proc ;实现 al内容的显示
push ax ;过程中使用了 AX,CX和 DX
push cx
push dx
push ax ;暂存 ax
mov dl,al ;转换 al的高 4位
mov cl,4
shr dl,cl
or dl,30h ;al高 4位变成 3
cmp dl,39h
jbe aldisp1
add dl,7 ;是 0Ah~ 0Fh,还要加上 7
aldisp1,mov ah,2 ;显示
int 21h
例 4.10 子程序- 1/3
pop dx ;恢复原 ax值到 dx
and dl,0fh ;转换 al的低 4位
or dl,30h
cmp dl,39h
jbe aldisp2
add dl,7
aldisp2,mov ah,2 ;显示
int 21h
pop dx
pop cx
pop ax
ret ;过程返回
ALdisp endp
例 4.10 子程序- 2/3
..,;主程序,同例 4.8源程序
mov bx,offset array;调用程序段开始
mov cx,count
displp,mov al,[bx]
call ALdisp ;调用显示过程
mov dl,',' ;显示一个逗号,分隔数据
mov ah,2
int 21h
inc bx
loop displp ;调用程序段结束
.exit 0
..,;过程定义
end
例 4.10 主程序- 3/3
HTOASC proc;将 AL低 4位表达的一位 16进制数转换为 ASCII码
and al,0fh
cmp al,9
jbe htoasc1
add al,37h ;是 0AH~ 0FH,加 37H
ret ;子程序返回
htoasc1,add al,30h ;是 0~ 9,加 30H
ret ;子程序返回
HTOASC endp
例题 具有多个出口的子程序;将 AL低 4位表达的一位 16进制数转换为 ASCII码
HTOASC proc
push bx
mov bx,offset ASCII ;BX指向 ASCII码表
and al,0fh ;取得一位 16进制数
xlat CS:ASCII;换码,AL←CS,[BX+ AL],注意数据在代码段 CS
pop bx
ret ;子程序返回
ASCII db 30h,31h,32h,33h,34h,35h,36h,37h
db 38h,39h,41h,42h,43h,44h,45h,46h
HTOASC endp
例题 具有局部变量的子程序因为数据区与子程序都在代码段,所以利用了换码指令 XLAT的另一种助记格式 ( 写出指向缓冲区的变量名,目的是便于指明段超越前缀 ) 。 串操作 MOVS,LODS和 CMPS指令也可以这样使用,以便使用段超越前缀除采用段超越方法外,子程序与主程序的数据段不同时,我们还可以通过修改 DS值实现数据存取;但需要保护和恢复 DS寄存器第 4章 4.4.2 子程序的参数传递
入口参数 ( 输入参数 ),主程序提供给子程序
出口参数 ( 输出参数 ),子程序返回给主程序
参数的形式:
① 数据本身 ( 传值 )
② 数据的地址 ( 传址 )
传递的方法:
① 寄存器 ② 变量 ③ 堆栈第 4章 例 4.11 求校验和
子程序计算数组元素的,校验和,
校验和是指不记进位的累加入口参数,数组的逻辑地址 ( 传址 )
元素个数 ( 传值 )
出口参数,求和结果 ( 传值 )
把参数存于约定的寄存器中,可以传值,也可以传址 。
子程序对带有出口参数的寄存器不能保护和恢复 ( 主程序视具体情况进行保护 )
子程序对带有入口参数的寄存器可以保护,也可以不保护;但最好一致例 4.11a
入口参数,CX=元素个数,
DS:BX=数组的段地址:偏移地址出口参数,AL=校验和用寄存器传递参数
.startup;设置入口参数 ( 含有 DS← 数组的段地址 )
mov bx,offset array;BX← 数组的偏移地址
mov cx,count ;CX← 数组的元素个数
call checksuma ;调用求和过程
mov result,al ;处理出口参数
.exit 0
例 4.11a 主程序
checksuma proc
xor al,al ;累加器清 0
suma,add al,[bx] ;求和
inc bx ;指向下一个字节
loop suma
ret
checksuma endp
end
例 4.11a 子程序
主程序和子程序直接采用同一个变量名共享同一个变量,实现参数的传递
不通模块间共享时,需要声明例 4.11b
入口参数:
count=元素个数,
array=数组名 ( 含段地址:偏移地址 )
出口参数:
result=校验和用变量传递参数;主程序
call checksumb;子程序
checksumb proc
push ax
push bx
push cx
xor al,al ;累加器清 0
mov bx,offset array;BX← 数组的偏移地址
mov cx,count;CX← 数组的元素个数例 4.11b- 1/2
sumb,add al,[bx] ;求和
inc bx
loop sumb
mov result,al ;保存校验和
pop cx
pop bx
pop ax
ret
checksumb endp
例 4.11b- 2/2
主程序将子程序的入口参数压入堆栈,
子程序从堆栈中取出参数
子程序将出口参数压入堆栈,主程序弹出堆栈取得它们例 4.11c
入口参数:
顺序压入偏移地址和元素个数出口参数:
AL=校验和用堆栈传递参数
.startup
mov ax,offset array
push ax
mov ax,count
push ax
call checksumc
add sp,4
mov result,al
.exit 0
例 4.11c 主程序图示要注意堆栈的分配情况,保证参数存取正确,子程序正确返回,并保持堆栈平衡
checksumc proc
push bp
mov bp,sp ;利用 BP间接寻址存取参数
push bx
push cx
mov bx,[bp+6] ;SS:[BP+6]指向偏移地址
mov cx,[bp+4] ;SS:[BP+6]指向元素个数
xor al,al
sumc,add al,[bx]
inc bx
loop sumc
pop cx
pop bx
pop bp
ret
checksumc endp
例 4.11c 子程序图示第 4章 子程序的嵌套子程序内包含有子程序的调用就是子程序嵌套没有什么特殊要求
ALdisp proc
push ax
push cx ;实现 al内容的显示
push dx
push ax ;暂存 ax
mov dl,al ;转换 al的高 4位
mov cl,4
shr dl,cl
call dldisp ;调用子程序显示 al高 4位
pop dx
and dl,0fh
call dldisp ;调用子程序显示 al低 4位
pop dx
pop cx
pop ax
ret
ALdisp endp
例 4.10 嵌套子程序- 1/2;显示 dl低 4位中一位十六进制数
dldisp proc
or dl,30h
cmp dl,39h
jbe dldisp1
add dl,7
dldisp1,mov ah,2
int 21h
ret
dldisp endp
例 4.10 嵌套子程序- 2/2
第 4章 子程序的递归
当子程序直接或间接地嵌套调用自身时称为递归调用,含有递归调用的子程序称为递归子程序
递归子程序必须采用寄存器或堆栈传递参数,递归深度受堆栈空间的限制例 4.12:求阶乘

01
0)!1(!
N
NNNN
.model small
.stack 256
.data
N dw 3
result dw?
.code
.startup
mov bx,N
push bx ;入口参数,N
call fact ;调用递归子程序
pop result ;出口参数,N!
.exit 0
例 4.12 主程序- 1/3
图示;计算 N!的近过程;入口参数:压入 N ;出口参数:弹出 N!
fact proc
push ax
push bp
mov bp,sp
mov ax,[bp+6] ;取入口参数 N
cmp ax,0
jne fact1 ;N> 0,N!= N× (N-1)!
inc ax ;N= 0,N!= 1
jmp fact2
例 4.12 递归子程序- 2/3
图示
fact1,dec ax ;N-1
push ax
call fact ;调用递归子程序求 (N-1)!
pop ax
mul word ptr [bp+6] ;求 N× (N-1)!
fact2,mov [bp+6],ax ;存入出口参数 N!
pop bp
pop ax
ret
fact endp
例 4.12 递归子程序- 3/3
图示第 4章 子程序的重入
子程序的重入是指子程序被中断后又被中断服务程序所调用,能够重入的子程序称为可重入子程序 。 在子程序中,注意利用寄存器和堆栈传递参数和存放临时数据,而不要使用固定的存储单元 ( 变量 ),就能够实现重入 。
子程序的重入不同于子程序的递归 。
重入是被动地进入,而递归是主动地进入;重入的调用间往往没有关系,
而递归的调用间却是密切相关的 。 递归子程序也是可重入子程序 。
第 4章 子程序补充例题 1
子程序从键盘输入一个有符号十进制数;子程序还包含将 ASCII码转换为二进制数的过程
输入时,负数用,-,引导,正数直接输入或用,+,引导
子程序用 寄存器传递出口参数,主程序调用该子程序输入 10个数据转换算法
.data
count = 10
array dw count dup(0) ;预留数据存储空间
.code
.startup
mov cx,count
mov bx,offset array
again,call read ;调用子程序输入一个数据
mov [bx],ax ;将出口参数存放缓冲区
inc bx
inc bx
call dpcrlf;调用子程序,光标回车换行以便输入下一个数据
loop again
.exit 0
补充例题 1- 1/5;输入有符号 10进制数的通用子程序;出口参数,AX=补码表示的二进制数值;说明:负数用,-,引导,正数用,+,引导或直接输入;数据范围是+ 32767~- 32768
read proc
push bx
push cx
push dx
xor bx,bx ;BX保存结果
xor cx,cx;CX为正负标志,0为正,- 1为负
mov ah,1 ;输入一个字符
int 21h
补充例题 1- 2/5
cmp al,'+' ;是,+,,继续输入字符
jz read1
cmp al,'-' ;是,-,,设置- 1标志
jnz read2 ;非,+,和,-,,转
read2
mov cx,-1
read1,mov ah,1 ;继续输入字符
int 21h
read2,cmp al,'0‘;不是 0~ 9之间的字符,则输入数据结束
jb read3
cmp al,'9'
ja read3
补充例题 1- 3/5
转换算法
sub al,30h;是 0~ 9之间的字符,则转换为二进制数;利用移位指令,实现数值乘 10,BX←BX × 10
shl bx,1
mov dx,bx
shl bx,1
shl bx,1
add bx,dx;
mov ah,0
add bx,ax;已输入数值乘 10后,与新输入数值相加
jmp read1 ;继续输入字符补充例题 1- 4/5
转换算法
read3,cmp cx,0
jz read4
neg bx ;是负数,进行求补
read4,mov ax,bx ;设置出口参数
pop dx
pop cx
pop bx
ret ;子程序返回
read endp;使光标回车换行的子程序
dpcrlf proc
..,;省略
dpcrlf endp
end
补充例题 1- 5/5
第 4章 子程序补充例题 2
子程序在屏幕上显示一个有符号 10进制数;子程序还包含将二进制数转换为 ASCII码的过程
显示时,负数用,-,引导,正数直接输出,没有前导字符
子程序的 入口参数用共享变量传递,
主程序调用该子程序显示 10个数据转换算法
.data
count = 10
array dw 1234,-1234,0,1,-1,32767
dw -32768,5678,-5678,9000
wtemp dw? ;共享变量
.code
.startup
mov cx,count
mov bx,offset array
again,mov ax,[bx]
mov wtemp,ax ;将入口参数存入共享变量
call write ;调用子程序显示一个数据
inc bx
inc bx
call dpcrlf ;便于显示下一个数据
loop again
.exit 0
补充例题 2- 1/5;显示有符号 10进制数的通用子程序;入口参数,共享变量 wtemp
write proc
push ax
push bx
push dx
mov ax,wtemp ;取出显示数据
test ax,ax ;判断零,正数或负数
jnz write1
mov dl,'0' ;是零,显示,0”后退出
mov ah,2
int 21h
jmp write5
补充例题 2- 2/5
write1,jns write2 ;是负数,显示,-,
mov bx,ax ;AX数据暂存于 BX
mov dl,'-'
mov ah,2
int 21h
mov ax,bx
neg ax ;数据求补 ( 求绝对值 )
write2,mov bx,10
push bx;10压入堆栈,作为退出标志补充例题 2- 3/5
转换算法
write3,cmp ax,0 ;数据 ( 余数 ) 为零
jz write4 ;转向显示
sub dx,dx ;扩展被除数 DX.AX
div bx ;数据除以 10,DX.AX÷ 10
add dl,30h;余数 ( 0~ 9) 转换为 ASCII码
push dx;数据各位先低位后高位压入堆栈
jmp write3
write4,pop dx;数据各位先高位后低位弹出堆栈
cmp dl,10 ;是结束标志 10,则退出
je write5
补充例题 2- 4/5
转换算法
mov ah,2 ;进行显示
int 21h
jmp write4
write5,pop dx
pop bx
pop ax
ret ;子程序返回
write endp;使光标回车换行的子程序
dpcrlf proc
..,;省略
dpcrlf endp
end
补充例题 2- 5/5
第 4章 子程序补充例题 3
子程序将 16位有符号二进制数求和,
然后除以数据个数得到平均值
子程序的 入口参数利用堆栈传递,主程序需要压入数据个数和数据缓冲区的偏移地址 。 子程序通过 BP寄存器从堆栈段相应位置取出参数
子程序的 出口参数用寄存器 AX传递
主程序提供 10个数据,并保存平均值避免溢出
.data
count = 10
array dw 1234,-1234,0,1,-1,32767
dw -32768,5678,-5678,9000
wmed dw? ; 存放平均值
.code
.startup
mov ax,count
push ax ;压入数据个数
mov ax,offset array
push ax ;压入缓冲区偏移地址
call mean ;调用子程序求平均值
add sp+4 ;平衡堆栈
mov wmed,ax ;保存平均值 ( 不含余数 )
.exit 0
补充例题 3- 1/4;计算 16位有符号数平均值子程序;入口参数,顺序压入数据个数和缓冲区偏移地址;出口参数,AX=平均值
mean proc
push bp
mov bp,sp
push bx ;保护寄存器
push cx
push dx
push si
push di
补充例题 3- 2/4
mov bx,[bp+4] ;从堆栈取出偏移地址
mov cx,[bp+6] ;从堆栈取数据个数
xor si,si ;SI保存求和的低 16位值
mov di,si ;DI保存求和的高 16位值
mean1,mov ax,[bx] ;取出一个数据 → AX
cwd ;符号扩展 → DX
add si,ax ;求和低 16位
adc di,dx ;求和高 16位
inc bx ;指向下一个数据
inc bx
loop mean1 ;循环补充例题 3- 3/4
避免溢出堆栈区
mov ax,si
mov dx,di ;累加和在 DX.AX
mov cx,[bp+6] ;数据个数在 CX
idiv cx;有符号数除法,求的平均值在 AX中,余数在 DX中
pop di ;恢复寄存器
pop si
pop dx
pop cx
pop bx
pop bp
ret
mean endp
end
补充例题 3- 4/4
第 4章 4.5 宏结构程序设计宏汇编重复汇编条件汇编
——统称宏结构宏( Macro)是汇编语言的一个特点,它是与子程序类似又独具特色的另一种简化源程序的方法宏 ——具有宏名的一段汇编语句序列
——宏定义 时书写宏指令 ——这段汇编语句序列的缩写
——宏调用 时书写宏展开 ——宏指令处用这段宏代替的过程
——宏汇编 时实现宏的参数 功能强大,颇具特色配合宏,还有 宏操作符 和有关伪指令
4.5.1 宏汇编第 4章宏定义 宏名 macro [形参表 ]
宏定义体
endm
mainbegin MACRO ;;定义名为 mainbegin的宏,无参数
mov ax,@data ;;宏定义体
mov ds,ax
ENDM ;;宏定义结束
mainend MACRO retnum ;;带有形参 retnum
mov al,retnum ;;宏定义中使用参数
mov ah,4ch
int 21h
ENDM
宏注释符宏调用 宏名 [实参表 ]
start,mainbegin ;宏调用,建立 DS内容
dispmsg string ;宏调用,显示字符串
mainend 0 ;宏调用,返回 DOS
end start
宏调用的实质是在汇编过程中进行宏展开
宏展开的具体过程是:当汇编程序扫描源程序遇到已有定义的宏调用时,即用相应的宏定义体取代源程序的宏指令,同时用位置匹配的实参对形参进行取代宏展开 宏展开 ——在汇编时,用宏定义体的代码序列替代宏指令的过程 。
start,mainbegin ;宏指令
1 mov ax,@data ;宏展开
1 mov ds,ax
mainend 0 ;宏指令
1 mov al,0 ;宏展开
1 mov ah,4ch
1 int 21h
宏的参数 宏的参数使用非常灵活宏定义时,
可以 无参数,例如 4.13a的 mainbegin
可以带有 一个参数,例如 4.13a的 mainend
也可以具有 多个参数 ;例如 4.14a的 shlext
参数可以是 常数,变量,存储单元,指令 ( 操作码 ) 或它们的一部分,也可以是 表达式 ;例如
4.14b的 shift和 shrot
宏定义体可以是任何合法的汇编语句,既可以是 硬指令序列,又可以是 伪指令序列 ;例如 4.15的
dstring;宏定义
shlext macro shloprand,shlnum
push cx
mov cl,shlnum
shl shloprand,cl
pop cx
endm;宏指令
shlext ax,6;宏展开
1 push cx
1 mov cl,06
1 shl ax,cl
1 pop cx
例 4.14a;统一 4条移位指令的宏指令
shift macro soprand,snum,sopcode
push cx
mov cl,snum
s&sopcode& soprand,cl
pop cx
endm;统一移位和循环移位 8条指令的宏指令
shrot macro sroprand,srnum,sropcode
push cx
mov cl,srnum
sropcode sroprand,cl
pop cx
endm
例 4.14b
替换操作符;宏定义
dstring macro string
db ’&string&’,0dh,0ah,’$’
endm;宏调用
dstring < This is a example,>
dstring < 0 !< Number !< 10 >;宏展开
1 db ’This is a example.’,0dh,0ah,’$’
1 db ’0 < Number < 10’,0dh,0ah,’$’
例 4.15
转义注释符传递注释符与宏有关的伪指令
局部标号伪指令
LOCAL 标号列表宏定义体采用了标号,应使用 LOCAL加以说明它必须是宏定义 MACRO语句之后的第一条语句
宏定义删除伪指令
PURGE 宏名表不需要某个宏定义时,可以把它删除
宏定义退出伪指令
EXITM
伪指令 EXITM表示结束当前宏调用的展开第 4章;宏定义
absol macro oprd
local next
cmp oprd,0
jge next
neg oprd
next:
endm
例 4.16 ;宏调用
absol word ptr [bx]
absol bx;宏展开
1 cmp word ptr [bx],0
1 jge0000
1 neg word ptr [bx]
10000:
1 cmp bx,0
1 jge0001
1 neg bx
10001:
单独占一行比较
仅是 源程序级 的 简化,
宏调用 在汇编时 进行程序语句的展开,不需要返回;不减小目标程序,执行速度没有改变
通过形参,实参结合实现参数传递,简捷直观,灵活多变
还是 目标程序级 的 简化,子程序调用 在执行时 由 CALL指令转向,
RET指令返回;形成的目标代码较短,执行速度减慢
需要利用寄存器,存储单元或堆栈等传递参数宏 子程序
宏与子程序具有各自的特点,程序员应该根据具体问题选择使用那种方法
通常,当程序段较短或要求较快执行时,
应选用宏;当程序段较长或为减小目标代码时,要选用子程序比较结论宏 子程序
4.5.2 重复汇编
重复汇编 指在汇编过程中,重复展开一段 ( 基本 ) 相同的语句
重复汇编没有名字,不能被调用
重复汇编常用在宏定义体中,也可以在一般汇编语句中使用
重复汇编伪指令有三个:
REPEAT——按参数值重复
FOR——按参数个数重复
FORC——按参数的字符个数重复
最后,用 ENDM结束第 4章按参数值重复 REPEAT 重复次数重复体
ENDM
char = 'A'
REPEAT 26
db char
char = char +1
ENDM
1 db char ;等效于 db 'A'
1 char = char +1
1 db char ;等效于 db 'B'
1 char = char +1
...
1 db char ;等效于 db 'Z'
1 char = char +1
按参数个数重复 FOR 形参,〈 实参表 〉
重复体
ENDM
FOR regad,<ax,bx,cx,dx>
push regad
ENDM
1 push ax
1 push bx
1 push cx
1 push dx
按参数字符个数重复 FORC 形参,字符串重复体
ENDM
FORC regad,dcba
pop &regad&x
ENDM
1 pop dx
1 pop cx
1 pop bx
1 pop ax
4.5.3 条件汇编
条件汇编伪指令在汇编过程中,根据条件决定汇编的语句
IFxx 表达式 ;满足,汇编分支语句体 1
分支语句体 1
[ ELSE ;不满足,汇编分支语句体 2
分支语句体 2 ]
ENDIF ;条件汇编结束第 4章
pdata macro num
IF num lt 100 ;;如果 num < 100,则汇编如下语句
db num dup (?)
ELSE ;;否则,汇编如下语句
db 100 dup (?)
ENDIF
endm
pdata 12 ;宏调用 ①
db 12 dup(?) ;宏汇编结果 ①
pdata 102 ;宏调用 ②
db 100 dup(?) ;宏汇编结果 ②
例 4.19
宏结构的作用宏汇编,重复汇编和条件汇编为源程序的编写提供了很多方便,
灵活运用它们可以编写出非常良好的源程序来汇编系统中有些以圆点起始的伪指令 ( 如,startup,.exit等 )
实际上是一种宏结构
dstring MACRO string ;;定义字符串
db '&string&',0dh,0ah,'$'
ENDM
mainbegin MACRO dsseg ;;设置数据段地址
mov ax,dsseg
mov ds,ax
ENDM
dispmsg MACRO message
mov dx,offset message
mov ah,09h
int 21h
ENDM
例题 4.13c- 1/3
mainend MACRO retnum ;;返回 DOS,可不带参数
ifb <retnum>
mov ah,4ch ;;没有参数
else
mov ax,4c00h+(retnum AND 0ffh);; 有参数
endif
int 21h
ENDM
例题 4.13c- 2/3
.model small
.stack 256
.data
msg1 equ this byte
dstring <Hello,Everybody !!>
msg2 equ this byte
dstring <You see,I made it.>
.code
start,mainbegin @data ;建立 DS内容
dispmsg msg1 ;显示 msg1字符串
dispmsg msg2 ;显示 msg2字符串
mainend ;返回 DOS
end start
例题 4.13c- 3/3
习题 7( p165)
4.27 4.41 4.44 4.50
将程序分段,采用子程序或宏结构都是进行模块化程序设计本节介绍开发大型程序时采用的方法:
源程序文件的包含目标模块连接子程序库
4.6 模块化程序设计例题 4.21
将键盘输入的数据按升序输出把源程序分放在几个文本文件中,在汇编时通过包含伪指令 INCLUDE结合成一体
INCLUDE 文件名
可将常用的子程序形成,ASM汇编语言源文件
可将常用的宏定义存放在,MAC宏库文件中
可将常量定义,声明语句组织在,INC包含文件中例 4.21a
① 宏库文件 lt421a.mac
② 主程序文件 lt421a.asm
③ 子程序文件 sub421a.asm
4.6.1 源程序文件的包含
1
dispchar macro char ;显示 char字符
mov dl,char
mov ah,2
int 21h
endm
dispmsg macro message ;显示 message字符串
mov dx,offset message
mov ah,9
int 21h
endm
Lt421a.mac
include lt421a.mac
...
dispmsg msg1 ;提示输入数据
mov bx,offset buf
call input ;数据输入
cmp cx,0
je start4 ;没有输入数据则退出
mov count,cx
..,;显示输入的数据
..,;数据排序
..,;显示经排序后的数据
start4,.exit 0
include sub421a.asm
end
Lt421a.asm
子程序源文件有 3个子程序
ALdisp ;显示 2位 16进制数子程序 ( 例 4.10)
sorting ;排序子程序 ( 例 4.8)
input ;键盘输入子程序还包含一个宏
convert ;;将 DX两位 ASCII码转换为两位 16进制数
sub421a.asm
让我们重点分析键盘输入子程序 input;键盘输入子程序;入口参数,ds:bx=存放数据的缓冲区;出口参数,cx=数据个数
input proc
push ax
push dx
xor cx,cx ;数据个数清 0
input01,xor dx,dx ;输入字符清 0
input02,mov ah,1 ;键盘输入一个字符
int 21h
input之一继续,input之二
input10,cmp al,0dh
je input30;是 回车,结束整个数据的输入
cmp al,’ ’
je input20;是 空格和逗号,确认输入了一个数据
cmp al,’,’
je input20
cmp al,08h
je input17;是 退格,丢弃本次输入的数据,出错
input之二继续,input之三
cmp al,’0’ ;有效数字判断 ( 图 4.5b)
jb input17 ;小于 ’ 0’,不是有效数字,出错
cmp al,’f’
ja input17 ;大于 ’ f’,不是有效数字
cmp al,’a’
jb input11
sub al,20h ;’a’ ~ ’ f’ 转 换 成 大写 ’ A’~ ’ F’
jmp input12
input11,cmp al,’F’
ja input17 ;字符小于 ’ a’,大于 ’ F’,出错
cmp al,’A’
jae input12 ;是 ’ A’~ ’ F’,有效字符
cmp al,’9’
ja input17 ;是 ’ 0’~ ’ 9’,有效字符
input之三继续,input之四
input12,cmp dl,0 ;有效字符的处理
jne input13
mov dl,al;dl=0,输入了一个数据的低位,则 dl←al
jmp input02 ;转到字符输入
input13,cmp dh,0
jne input17;dl≠ 0,dh≠ 0输入 3位数据,出错
mov dh,dl;dl≠ 0,dh= 0输入了一个数据的高位
mov dl,al ;dh←dl,dl←al
jmp input02 ;转到字符输入
input之四继续,input之五
input17,mov dl,7 ;输入错误处理
mov ah,2
int 21h
mov dl,'?'
mov ah,2
int 21h
jmp input01 ;转到输入一个数据
input之五继续,input之六;转换正确的输入数据 ( 图 4.5c)
input20,convert
jmp input01 ;转到输入一个数据
input30,convert
pop dx
pop ax
ret ;返回,出口参数已设定
input endp
input之六继续,convert之一;;将 DX两位 ASCII码转换为两位 16进制数 ( 图 4.5c)
convert macro
local input21,input22
local input24,input25
cmp dl,0;;dl=0,没有要转换的数据,退出
je input25
convert之一继续,convert之二
cmp dl,'9'
jbe input21
sub dl,7 ;;字符 A~ F,则减 7
input21,and dl,0fh ;;转换低位
cmp dh,0 ;;dh=0,没有高位数据
je input24
cmp dh,'9'
jbe input22
sub dh,7
input22,shl dh,1
shl dh,1
shl dh,1
shl dh,1 ;;转换高位
or dl,dh ;;合并高,低位
convert之二继续,convert之三源文件包含的操作步骤:
① 分别编辑生成各个文件
② 汇编,连接主程序文件
input24,mov [bx],dl ;;存入缓冲区
inc bx
inc cx ;;数据加 1
input25:
endm
convert之三把常用子程序写成独立的源程序文件,单独汇编,形成子程序的目标文件,OBJ
主程序也经过独立汇编之后形成目标文件连接程序将所有目标文件连接起来,最终产生可执行文件需要遵循的原则:
① 声明共用的变量,过程等
② 实现正确的段组合
③ 处理好参数传递问题
4.6.2 目标代码文件的连接
2
声明共用的变量,过程
各个模块间共用的变量,过程等要说明
PUBLIC 标识符 [,标识符,..];定义标识符的模块使用
EXTERN 标识符,类型 [,标识符,类型,..];调用标识符的模块使用
标识符是变量名,过程名等
类型是 byte / word / dword( 变量 ) 或 near / far
( 过程 )
在一个源程序中,public/extern语句可以有多条
各模块间的 public/extern伪指令要互相配对,并且指明的类型互相一致第 4章实现正确的段组合
子程序文件必须定义在代码段中,也可以具有局部的数据变量
采用简化段定义格式,只要采用相同的存储模式,
容易实现正确的近或远调用
完整段定义格式中,为了实现模块间的段内近调用
( near类型 ),各自定义的段名,类别必须相同,
组合类型都是 public。 实际的程序开发中,各个模块往往由不同的程序员完成,不易实现段同名或类别相同,所以索性定义成远调用 ( far类型 )
定义数据段时,同样也要注意这个问题 。 当各个模块的数据段不同时,要正确设置数据段 DS寄存器的段基地址第 4章处理好参数传递问题
少量参数可用寄存器或堆栈直接传送数据本身
大量数据可以安排在缓冲区,用寄存器或堆栈传送数据的存储地址
还可利用变量传递参数,但是要采用
public/extern声明为公共 ( 全局 ) 变量
这些也是子程序间的参数传递方法
另外,第 6章混合编程介绍了更好的堆栈传递参数方法,可以采用第 4章例 4.21b
主程序 lt421b.asm并入宏定义
子程序文件 sub421b.asm,要加上段定义语句,声明语句等,
但不需要起始点和结束点模块连接的操作步骤:
① 分别编辑生成各个文件
② 分别汇编各个文件
③ 连接各个目标文件,形成可执行文件把常用子程序写成独立的源文件,单独汇编形成 OBJ文件后,存入 子程序库主程序也单独汇编形成 OBJ文件主程序连接时,调入子程序库中的子程序模块,产生最终的可执行文件例 4.21c
① 主程序文件 lt421c.asm
② 子程序文件 sub421c1.asm
③ 子程序文件 sub421c2.asm
④ 子程序文件 sub421c3.asm
4.6.3 子程序库的调入
3
..,;宏定义
.code
extern ALdisp:near,sorting:near,input:near;声明其他模块中的子程序
.startup
...
.exit 0
end
Lt421c.asm
.model small
.code
public aldisp
Aldisp proc
...
Aldisp endp
end
sub421c1.asm
.model small
.code
public sorting
sorting proc
...
sorting endp
end
sub421c2.asm
.model small
.code
public input
input proc
...
input endp
end
sub421c3.asm
库文件调入的操作步骤:
① 分别编辑生成各个文件
② 分别汇编各个文件
③ 用库管理文件,将子程序模块添加到库文件 (,LIB) 中
④ 连接主程序,提供库文件,
形成可执行文件第 4章 补充例题
将子程序补充例题 1~ 3的子程序编写成模块,供主程序调用
实现功能:从键盘输入有符号 10进制数,求它们的平均值,然后输出
源程序文件进行简单修改
主程序文件
子程序文件
利用目标代码文件的连接形成可执行文件第 4章 教学要求 ( 1)
1,掌握基本程序结构 ―― 顺序结构,分支结构,
循环结构,子程序和宏及其汇编语言程序设计
2,了解宏操作符,条件汇编和重复汇编,源程序包含,代码连接和子程序库等程序设计方法
3,掌握伪指令,PROC/ENDP,MACRO/ENDM,LOCAL;
INCLUDE/ PUBLIC/ EXTERN
4,了解伪指令,REPEAT/ FOR/ FORC,IFxx
第 4章 教学要求 ( 2)
5,熟悉常见程序设计问题:
多精度运算,查表 ( 查代码,特定值等 )
ASCII,BCD及十六进制数据间的代码转换数据范围判断 ( 0~ 9,A~ Z,a~ z)
字母大小写转换;字符串传送,比较等操作求最小最大值,数据求和,统计字符个数子程序的寄存器和共享变量传递参数放松一下