第 4 章教学重点综合应用第 2章硬指令和第
3章伪指令,第 4章从程序结构角度展开程序设计,重点掌握:
分支结构程序设计
循环结构程序设计
子程序结构程序设计第 4 章 4.1 顺序程序设计
顺序程序完全按指令书写的前后顺序执行每一条指令,是最基本,
最常见的程序结构例 4.1 计算例 4.2 移位例题 代码转换例 4.1
.model small
.stack
.data
X dw 5
Y dw 6
Z dw 7
W dw?
.code
.startup
mov ax,X
add ax,Y
add 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
.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提供的条件控制伪指令实现单分支:求绝对值等双分支:例 4.3等多分支:例 4.4等第 4 章 单分支程序设计
条件成立跳转,否则顺序执行分支语句体;注意选择正确的条件转移指令和转移目标地址例题 求绝对值;计算 AX的绝对值
cmp ax,0
jns nonneg ;分支条件,AX≥ 0
neg ax ;条件不满足,求补
nonneg:mov result,ax ;条件满足;计算 AX的绝对值
cmp ax,0
jl yesneg ;分支条件,AX< 0
jmp nonneg
yesneg:neg ax ;条件不满足,求补
nonneg:mov result,ax ;条件满足
Good
Bad
例题 无符号数除以 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
的特殊情况第 4 章 双分支程序设计
条件成立跳转执行第 2个分支语句体,
否则顺序执行第 1个分支语句体 。 注意第
1个分支体后一定要有一个 JMP指令跳到第 2个分支体后例题 显示 BX最高位 -1
shl bx,1 ;BX最高位移入 CF
jc one ;CF= 1,即最高位为 1,转移
mov dl,’0’;CF= 0,即最高位为 0,DL← ’0’
jmp two ;一定要跳过另一个分支体
one,mov dl,’1’ ;DL← ’1’
two,mov ah,2
int 21h ;显示例题 显示 BX最高位 -2
shl bx,1 ;BX最高位移入 CF
jnc one ;CF= 0,即最高位为 0,转移
mov dl,’1’;CF= 1,即最高位为 1,DL← ’1’
jmp two ;一定要跳过另一个分支体
one,mov dl,’0’ ;DL← ’0’
two,mov ah,2
int 21h ;显示例题 显示 BX最高位 -3
mov dl,’0’ ;DL← ’0’
shl bx,1 ;BX最高位移入 CF
jnc two ;CF= 0,最高位为 0,转移
mov dl,’1’ ;CF= 1,最高位为 1,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,...
例题 单分支和双分支第 4 章 多分支程序设计
多个条件对应各自的分支语句体,哪个条件成立就转入相应分支体执行 。 多分支可以化解为双分支或单分支结构的组合,例如:
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 章 地址表形成多分支
需要在数据段事先安排一个按顺序排列的转移地址表
输入的数字作为偏移量 。 因为只有 2个字节 16位偏移地址,所以偏移量需要乘 2
关键是要理解间接寻址方式 JMP指令地址表 分支 1地址 分支 2地址,..
table dw 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
.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.16- 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.16- 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 si,offset string
outlp,cmp byte ptr [di],’$’;外循环,先判断后循环
jz done ;为 $结束
cmp byte ptr [si],’ ’;检测是否是空格
jnz next ;不是空格继续循环例 4.9 剔除空格- 1/2
mov di,si ;是空格,进入剔除空格分支;该分支是循环程序段
inlp,inc di
mov al,[di] ;前移一个位置
mov [di-1],al
cmp byte ptr [di],’$’;内循环,先循环后判断
jnz inlp
jmp outlp
next,inc si ;继续对后续字符进行处理
jmp outlp
done,.exit 0 ;结束例 4.9 剔除空格- 2/2 条件控制双重循环第 4 章 4.4 子程序设计
把功能相对独立的程序段单独编写和调试,作为一个相对独立的模块供程序使用,就形成子程序
子程序可以实现源程序的模块化,可简化源程序结构,可以提高编程效率子程序设计要利用过程定义伪指令参数传递是子程序设计的重点和难点子程序可以嵌套;
一定条件下,还可以递归和重入
4.4.1 过程定义伪指令过程名 proc [near|far]
...
过程名 endp
过程名 ( 子程序名 ) 为符合语法的标识符
NEAR属性 ( 段内近调用 ) 的过程只能被相同代码段的其他程序调用
FAR属性 ( 段间远调用 ) 的过程可以被相同或不同代码段的程序调用
对简化段定义格式,在微型,小型和紧凑存储模式下,过程的缺省属性为 near;在中型,大型和巨型存储模式下,过程的缺省属性为 far
对完整段定义格式,过程的缺省属性为 near
用户可以在过程定义时用 near或 far改变缺省属性第 4 章 子程序的常见格式
subname proc ;具有缺省属性的 subname过程
push ax ;保护寄存器,顺序压入堆栈
push bx ;ax/bx/cx仅是示例
push cx
… ;过程体
pop cx ;恢复寄存器,逆序弹出堆栈
pop bx
pop ax
ret ;过程返回
subname endp ;过程结束;子程序功能:实现光标回车换行
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
例题 具有多个出口的子程序第 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+4]指向元素个数
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 ax ;暂存 ax
mov cl,4
shr al,cl ;转换 al的高 4位
call htoasc ;子程序调用 ( 嵌套 )
pop ax ;转换 al的低 4位
call htoasc ;子程序调用 ( 嵌套 )
pop cx
pop ax
ret
ALdisp endp
例 4.10 嵌套子程序- 1/3;将 AL低 4位表达的一位 16进制数转换为 ASCII码
HTOASC proc
push ax
push bx
push dx
mov bx,offset ASCII;BX指向 ASCII码表
and al,0fh ;取得一位 16进制数
xlat ASCII;换码,AL←CS,[BX+ AL],注意数据在代码段 CS
例 4.10 嵌套子程序- 2/3
mov dl,al ;显示
mov ah,2
int 21h
pop dx
pop bx
pop ax
ret ;子程序返回;子程序的数据区
ASCII db 30h,31h,32h,33h,34h,35h,36h,37h
db 38h,39h,41h,42h,43h,44h,45h,46h
HTOASC endp
例 4.10 嵌套子程序- 3/3
第 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 章 例题 4.13:从键盘输入有符号十进制数
子程序从键盘输入一个有符号十进制数;子程序还包含将 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
例题 4.13- 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
例题 4.13- 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
例题 4.13- 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 ;继续输入字符例题 4.13- 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
例题 4.13- 5/5
第 4 章 例题 4.14:显示有符号十进制数
子程序在屏幕上显示一个有符号十进制数;子程序还包含将二进制数转换为 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
例题 4.14- 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
例题 4.14- 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压入堆栈,作为退出标志例题 4.14- 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
例题 4.14- 4/5
转换算法
mov ah,2 ;进行显示
int 21h
jmp write4
write5,pop dx
pop bx
pop ax
ret ;子程序返回
write endp;使光标回车换行的子程序
dpcrlf proc
..,;省略
dpcrlf endp
end
例题 4.14- 5/5
第 4 章 例题 4.15:计算有符号数平均值
子程序将 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
例题 4.15- 1/4;计算 16位有符号数平均值子程序;入口参数,顺序压入数据个数和缓冲区偏移地址;出口参数,AX=平均值
mean proc
push bp
mov bp,sp
push bx ;保护寄存器
push cx
push dx
push si
push di
例题 4.15- 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 ;循环例题 4.15- 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
例题 4.15- 4/4
第 4章 教学要求
1,掌握基本程序结构 ―― 顺序结构,分支结构,循环结构,子程序及其汇编语言程序设计
2,熟悉常见程序设计问题:
多精度运算,查表 ( 查代码,特定值等 )
ASCII,BCD及十六进制数据间的代码转换数据范围判断 ( 0~ 9,A~ Z,a~ z)
字母大小写转换;字符串传送,比较等操作求最小最大值,数据求和,统计字符个数子程序的寄存器和共享变量传递参数
3章伪指令,第 4章从程序结构角度展开程序设计,重点掌握:
分支结构程序设计
循环结构程序设计
子程序结构程序设计第 4 章 4.1 顺序程序设计
顺序程序完全按指令书写的前后顺序执行每一条指令,是最基本,
最常见的程序结构例 4.1 计算例 4.2 移位例题 代码转换例 4.1
.model small
.stack
.data
X dw 5
Y dw 6
Z dw 7
W dw?
.code
.startup
mov ax,X
add ax,Y
add 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
.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提供的条件控制伪指令实现单分支:求绝对值等双分支:例 4.3等多分支:例 4.4等第 4 章 单分支程序设计
条件成立跳转,否则顺序执行分支语句体;注意选择正确的条件转移指令和转移目标地址例题 求绝对值;计算 AX的绝对值
cmp ax,0
jns nonneg ;分支条件,AX≥ 0
neg ax ;条件不满足,求补
nonneg:mov result,ax ;条件满足;计算 AX的绝对值
cmp ax,0
jl yesneg ;分支条件,AX< 0
jmp nonneg
yesneg:neg ax ;条件不满足,求补
nonneg:mov result,ax ;条件满足
Good
Bad
例题 无符号数除以 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
的特殊情况第 4 章 双分支程序设计
条件成立跳转执行第 2个分支语句体,
否则顺序执行第 1个分支语句体 。 注意第
1个分支体后一定要有一个 JMP指令跳到第 2个分支体后例题 显示 BX最高位 -1
shl bx,1 ;BX最高位移入 CF
jc one ;CF= 1,即最高位为 1,转移
mov dl,’0’;CF= 0,即最高位为 0,DL← ’0’
jmp two ;一定要跳过另一个分支体
one,mov dl,’1’ ;DL← ’1’
two,mov ah,2
int 21h ;显示例题 显示 BX最高位 -2
shl bx,1 ;BX最高位移入 CF
jnc one ;CF= 0,即最高位为 0,转移
mov dl,’1’;CF= 1,即最高位为 1,DL← ’1’
jmp two ;一定要跳过另一个分支体
one,mov dl,’0’ ;DL← ’0’
two,mov ah,2
int 21h ;显示例题 显示 BX最高位 -3
mov dl,’0’ ;DL← ’0’
shl bx,1 ;BX最高位移入 CF
jnc two ;CF= 0,最高位为 0,转移
mov dl,’1’ ;CF= 1,最高位为 1,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,...
例题 单分支和双分支第 4 章 多分支程序设计
多个条件对应各自的分支语句体,哪个条件成立就转入相应分支体执行 。 多分支可以化解为双分支或单分支结构的组合,例如:
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 章 地址表形成多分支
需要在数据段事先安排一个按顺序排列的转移地址表
输入的数字作为偏移量 。 因为只有 2个字节 16位偏移地址,所以偏移量需要乘 2
关键是要理解间接寻址方式 JMP指令地址表 分支 1地址 分支 2地址,..
table dw 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
.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.16- 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.16- 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 si,offset string
outlp,cmp byte ptr [di],’$’;外循环,先判断后循环
jz done ;为 $结束
cmp byte ptr [si],’ ’;检测是否是空格
jnz next ;不是空格继续循环例 4.9 剔除空格- 1/2
mov di,si ;是空格,进入剔除空格分支;该分支是循环程序段
inlp,inc di
mov al,[di] ;前移一个位置
mov [di-1],al
cmp byte ptr [di],’$’;内循环,先循环后判断
jnz inlp
jmp outlp
next,inc si ;继续对后续字符进行处理
jmp outlp
done,.exit 0 ;结束例 4.9 剔除空格- 2/2 条件控制双重循环第 4 章 4.4 子程序设计
把功能相对独立的程序段单独编写和调试,作为一个相对独立的模块供程序使用,就形成子程序
子程序可以实现源程序的模块化,可简化源程序结构,可以提高编程效率子程序设计要利用过程定义伪指令参数传递是子程序设计的重点和难点子程序可以嵌套;
一定条件下,还可以递归和重入
4.4.1 过程定义伪指令过程名 proc [near|far]
...
过程名 endp
过程名 ( 子程序名 ) 为符合语法的标识符
NEAR属性 ( 段内近调用 ) 的过程只能被相同代码段的其他程序调用
FAR属性 ( 段间远调用 ) 的过程可以被相同或不同代码段的程序调用
对简化段定义格式,在微型,小型和紧凑存储模式下,过程的缺省属性为 near;在中型,大型和巨型存储模式下,过程的缺省属性为 far
对完整段定义格式,过程的缺省属性为 near
用户可以在过程定义时用 near或 far改变缺省属性第 4 章 子程序的常见格式
subname proc ;具有缺省属性的 subname过程
push ax ;保护寄存器,顺序压入堆栈
push bx ;ax/bx/cx仅是示例
push cx
… ;过程体
pop cx ;恢复寄存器,逆序弹出堆栈
pop bx
pop ax
ret ;过程返回
subname endp ;过程结束;子程序功能:实现光标回车换行
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
例题 具有多个出口的子程序第 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+4]指向元素个数
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 ax ;暂存 ax
mov cl,4
shr al,cl ;转换 al的高 4位
call htoasc ;子程序调用 ( 嵌套 )
pop ax ;转换 al的低 4位
call htoasc ;子程序调用 ( 嵌套 )
pop cx
pop ax
ret
ALdisp endp
例 4.10 嵌套子程序- 1/3;将 AL低 4位表达的一位 16进制数转换为 ASCII码
HTOASC proc
push ax
push bx
push dx
mov bx,offset ASCII;BX指向 ASCII码表
and al,0fh ;取得一位 16进制数
xlat ASCII;换码,AL←CS,[BX+ AL],注意数据在代码段 CS
例 4.10 嵌套子程序- 2/3
mov dl,al ;显示
mov ah,2
int 21h
pop dx
pop bx
pop ax
ret ;子程序返回;子程序的数据区
ASCII db 30h,31h,32h,33h,34h,35h,36h,37h
db 38h,39h,41h,42h,43h,44h,45h,46h
HTOASC endp
例 4.10 嵌套子程序- 3/3
第 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 章 例题 4.13:从键盘输入有符号十进制数
子程序从键盘输入一个有符号十进制数;子程序还包含将 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
例题 4.13- 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
例题 4.13- 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
例题 4.13- 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 ;继续输入字符例题 4.13- 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
例题 4.13- 5/5
第 4 章 例题 4.14:显示有符号十进制数
子程序在屏幕上显示一个有符号十进制数;子程序还包含将二进制数转换为 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
例题 4.14- 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
例题 4.14- 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压入堆栈,作为退出标志例题 4.14- 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
例题 4.14- 4/5
转换算法
mov ah,2 ;进行显示
int 21h
jmp write4
write5,pop dx
pop bx
pop ax
ret ;子程序返回
write endp;使光标回车换行的子程序
dpcrlf proc
..,;省略
dpcrlf endp
end
例题 4.14- 5/5
第 4 章 例题 4.15:计算有符号数平均值
子程序将 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
例题 4.15- 1/4;计算 16位有符号数平均值子程序;入口参数,顺序压入数据个数和缓冲区偏移地址;出口参数,AX=平均值
mean proc
push bp
mov bp,sp
push bx ;保护寄存器
push cx
push dx
push si
push di
例题 4.15- 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 ;循环例题 4.15- 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
例题 4.15- 4/4
第 4章 教学要求
1,掌握基本程序结构 ―― 顺序结构,分支结构,循环结构,子程序及其汇编语言程序设计
2,熟悉常见程序设计问题:
多精度运算,查表 ( 查代码,特定值等 )
ASCII,BCD及十六进制数据间的代码转换数据范围判断 ( 0~ 9,A~ Z,a~ z)
字母大小写转换;字符串传送,比较等操作求最小最大值,数据求和,统计字符个数子程序的寄存器和共享变量传递参数