循环之循环
1. 课程说明
本课程基于《汇编语言(第 2 版)》郑晓薇 编著,机械工业出版社,可以配合该教材使用。本课程由郑晓薇授权制作,提取教材中的实例以及实验内容,可以在实验楼环境中完成所有实例及实验。实验课程制作符合教材原版实例驱动教学以及实验训练贯穿始终的特点。
本教材可在 当当网 购买(点击链接即可进入购买页面)。
本课程实验列表中的最后模块为配套教材的习题参考答案,可供读者学习参考。
2. 实验环境
DOS 环境:
实验环境中安装有 dosemu
可以模拟 DOS 环境,并提供 DEBUG
、MASM
、LINK
等汇编语言开发程序。
3. 循环程序设计
在汇编语言中,程序的循环可以用分支转移指令实现,也可以用 8086 指令系统中提供的专门的循环指令 LOOP,这样使程序更清晰、简便。
除了循环指令之外,还有很多地方用到了循环的概念。例如串处理,需要对串中的字符循环地进行操作(示例 6-2 ~ 示例 6-4)。
3.1 循环程序例一
本例子为教材的示例 6-1。
- **
关注点
**:直接写显存的方法 - **
关注点
**:串处理指令的用法
设计目标
在 5 行 16 列上用写显存方法显示多彩字符串。用循环指令实现。
设计思路
(1)用 DH 存放行号,DL 存放列号;
(2)BL 存放字符属性,第 1 个字符的属性为 4,红色;其他字符按属性 +1 改变;
(3)字符的位置计算公式:行号 × 160 + 列号 × 2;
(4)用循环指令 LOOP 实现将多彩字符串循环写入显存 。
实验步骤
(1)双击桌面上的记事本 gedit
,录入下列程序:
;6-1.asm 在5行16列上用写显存方法显示多彩字符串。
data segment
a1 db 'Hello world!'
a2 db 0
data ends
code segment
assume cs:code,ds:data
start:
mov ax,data
mov ds,ax
mov dh,5 ;行
mov dl,16 ;列
mov bl,4 ;颜色属性
mov si,0
show_str:
mov ax,0b800h ;显存首址
mov es,ax
mov ax,160
mul dh ;行号×160
mov di,ax ;起始行位置
sal dl,1 ;列号×2,2个字节单元为1列
mov dh,0
add di,dx ;行列相加
mov cx,a2-a1 ;字符串长度
let1: mov al,[si] ;循环写字符和属性到显存
mov es:[di],al
mov byte ptr es:[di+1],bl
inc si
inc bl ;属性加1
add di,2 ;写完即显示完
loop let1 ;循环指令
mov ah,4ch
int 21h
code ends
end start
(2)在 dos 子目录下保存为 6-1.asm,经过汇编 masm 6-1.asm
,连接 link 6-1.obj
,生成 6-1.exe。
D:\dos〉masm 6-1.asm
D:\dos〉link 6-1.obj
D:\dos〉6-1.exe
(3)运行结果:
3.2 循环程序例二
本例子为教材的示例 6-2。
设计目标
将数据段中的字符串 STRG1 传送到附加段的 STRG2 中。
设计思路
(1) 分别定义数据段 DATA 和附加段 EXTRA;
(2) 用 SI 保存源串 STRG1 的偏移地址,DI 保存目的串 STRG2 的偏移地址,传送个数由 CX 指出;
(3) 用 CLD
指令将方向标志 DF 清 0,以便从低地址单元开始取数;存储单元地址自动增加,取下一数;
(4) 用 REP MOVSB
指令实现串传送。
实验步骤
(1)双击桌面上的记事本 gedit
,录入下列程序:
;6-2.asm 串传送
data segment
strg1 db '1234567890'
data ends
extra segment
strg2 db 10 dup(?)
extra ends
code segment
assume cs:code,ds:data,es:extra
start:
mov ax,data
mov ds,ax
mov ax,extra
mov es,ax
lea si,strg1 ;源串首地址
lea di,strg2 ;目的串首地址
cld ;方向标志清0
mov cx,10
rep movsb ;以字节形式重复传送CX次
mov ah,4ch
int 21h
code ends
end start
(2)在 dos 子目录下保存为 6-2.asm,经过汇编 masm 6-2.asm
,连接 link 6-2.obj
,生成 6-2.exe。
D:\dos〉masm 6-2.asm
D:\dos〉link 6-2.obj
D:\dos〉6-2.exe
D:\dos〉debug 6-2.exe
(3)运行结果:
执行后屏幕上没有显示结果。要观察运行结果,采用 DEBUG 执行 6-2.EXE。在 DEBUG 下,用 U 命令查看,找到断点 0018,用 G 0018
执行,再用 D ES:0
命令查看传送结果。
观察一下数据段的段地址和附加段的段地址是什么。用 D 命令查看数据是否已经传送到附加段了。
**思考
**:如果采用循环指令实现串传送,程序如何编写?
3.3 循环程序例三
本例子为教材的示例 6-3。
设计目标
比较两个字串 BUNCH1 和 BUNCH2 是否相同,相同打印 Y,不相同打印 N。
实验步骤
(1)双击桌面上的记事本 gedit
,录入下列程序:
;6-3.asm 比较两个字串BUNCH1和BUNCH2
data segment
bunch1 db 'student'
bunch2 db 'studEnt'
data ends
code segment
assume cs:code,ds:data,es:data
start:
mov ax,data
mov ds,ax
mov es,ax ;附加段与数据段为同一段
lea si,bunch1
lea di,bunch2
cld
mov cx,7
repe cmpsb
jz let1 ;相等转LET1
mov dl,'n' ;不相等,显示N
jmp print
let1:
mov dl,'y' ;相等,显示Y
print:
mov ah,2h
int 21h
mov ah,4ch
int 21h
code ends
end start
(2)在 dos 子目录下保存为 6-3.asm,经过汇编 masm 6-3.asm
,连接 link 6-3.obj
,生成 6-3.exe。
D:\dos〉masm 6-3.asm
D:\dos〉link 6-3.obj
D:\dos〉6-3.exe
(3)运行结果:
在 DOS 下执行 6-3.exe 后,屏幕上显示出 “n”。
3.4 循环程序例四
本例子为教材的示例 6-4。
设计目标
在字数组 VALUE 中查找 -1,找到后将其位置保存到 ADDR 单元。
实验步骤
(1)双击桌面上的记事本 gedit
,录入下列程序:
;6-4.asm 串扫描。在字数组VALUE中查找-1,找到后将其位置保存到ADDR单元。
extra segment
value dw 1,2,0,3,5,-1,10
addr dw ?
extra ends
code segment
assume cs:code,es:extra
start:
mov ax,extra
mov es,ax ;附加段段地址
mov ax,-1 ;查找-1
lea di,value ;串的偏移地址由di指出
cld
mov cx,7 ;7个数值
repnz scasw
sub di,2 ;找到后将di减2
mov addr,di ;将其位置保存
mov ah,4ch
int 21h
code ends
end start
(2)在 dos 子目录下保存为 6-4.asm,经过汇编 masm 6-4.asm
,连接 link 6-4.obj
,生成 6-4.exe。
D:\dos〉masm 6-4.asm
D:\dos〉link 6-4.obj
D:\dos〉6-4.exe
D:\dos〉debug 6-4.exe
(3)运行结果:
执行后屏幕上没有显示结果。要观察运行结果,采用 DEBUG 执行 6-4.EXE。在 DEBUG 下,用 U 命令查看,找到断点,用 G 断点执行,再用 D ES:0 命令查看 ADDR 单元中的结果。
**提问
**:ADDR 是几号单元?
3.5 循环程序例五
本例子为教材的示例 6-5。
**关注点
**:排序算法需要用双重循环
设计目标
将字数组 PART 按升序排序。
设计思路
(1) 用两条 LOOP 指令实现双重循环时,对 CX 寄存器有冲突。采用 PUSH CX
指令将外循环的 CX 值入栈保存,内循环的 LOOP 结束后,再将外循环的 CX 恢复;
(2)用寄存器相对寻址取出两数进行比较。
程序框图
实验步骤
(1)双击桌面上的记事本 gedit
,录入下列程序:
; 6-5.asm 将字数组part按升序排序
data segment
part dw 15,32,6,-27,8
sign dw ?
data ends
code segment
assume cs:code,ds:data
start:
mov ax,data
mov ds,ax
mov cx,sign-part ;数组长度
shr cx,1 ;元素个数
dec cx
loop1: ;外循环
push cx ;保存外循环次数
mov bx,0
loop2: ;内循环
mov ax,part[bx]
cmp ax,part[bx+2] ;比较大小
jle next ;升序
xchg ax,part[bx+2] ;交换
mov part[bx],ax
next:add bx,2
loop loop2
pop cx ;恢复外循环次数
loop loop1
mov ah,4ch
int 21h
code ends
end start
(2)在 dos 子目录下保存为 6-5.asm,经过汇编 masm 6-5.asm
,连接 link 6-5.obj
,生成 6-5.exe。
D:\dos〉masm 6-5.asm
D:\dos〉link 6-5.obj
D:\dos〉6-5.exe
D:\dos〉debug 6-5.exe
(3)运行结果:
执行后屏幕上没有显示结果。要观察运行结果,采用 DEBUG 执行 6-5.EXE。在 DEBUG 下,用 U 命令查看,找到断点,用 G 断点执行,再用 D DS:0
命令查看排序结果。
**练习
**:数组 TABLE 中存放 8 个小写字母 computer。编程序,将它们按降序排序。
3.6 循环程序例六
本例子为教材的示例 6-6。
设计目标
编程序实现两个多字节数相加运算。
Z = X + Y ,设 X = 5488114433225634H,Y = 3499754783645231H,则 Z = 8921868BB686A865H
设计思路
(1)在数据段中定义两个多字节变量,低字节单元存放低位,高字节单元存放高位;
(2)字节的个数 N 采用 EQU 赋值伪指令获得;
(3)多字节相加用带进位加指令 ADC;
实验步骤
(1)双击桌面上的记事本 gedit
,录入下列程序:
; 6-6.asm 两个多字节数相加运算
data segment
x db 34h,56h,22h,33h,44h,11h,88h,54h
y db 31h,52h,64h,83h,47h,75h,99h,34h
n equ $-y ;n=字节个数
z db n dup(?)
data ends
code segment
assume cs:code,ds:data
start: mov ax,data
mov ds,ax
mov cx,n
mov bx,0
clc ;将进位标志CF清0
let1:
mov al,x[bx] ;从低位开始带进位加
adc al,y[bx]
mov z[bx],al
inc bx
loop let1
mov ah,4ch
int 21h
code ends
end start
(2)在 dos 子目录下保存为 6-6.asm,经过汇编 masm 6-6.asm
,连接 link 6-6.obj
,生成 6-6.exe。
D:\dos〉masm 6-6.asm
D:\dos〉link 6-6.obj
D:\dos〉6-6.exe
D:\dos〉debug 6-6.exe
(3)运行结果:执行后屏幕上没有显示结果。要观察运行结果,采用 DEBUG 执行 6-6.EXE。在 DEBUG 下,用 U 命令查看,找到断点,用 G 断点执行,再用 D DS:0
命令查看结果。
可以看出,0 号 ~ 7 号这 8 个字节单元是 X,8 号 ~ F 号 8 个单元是 Y,10H 号 ~ 17H 号单元中为相加的结果 Z。
3.7 循环程序例七
本例子为教材的示例 6-7。
设计目标
求出 X 字节数组中的最大数放入 MAX 单元。
设计思路
(1)先设定一个 MAX,依次从数组中取出元素与之比较,若大于 MAX,将该数送入 MAX,直至数组结束;
(2)用 MAX 和 X 单元地址相减获得数组元素个数;
实验步骤
(1)双击桌面上的记事本 gedit
,录入下列程序:
; 6-7.asm 求X数组中的最大数放入MAX。
data segment
x db 12,4,55,32,26
max db 0
data ends
code segment
assume cs:code,ds:data
start: mov ax,data
mov ds,ax
mov cx,max-x ;数组长度
mov bx,0
let1: mov al,x[bx]
cmp al,max
jle let2
mov max,al ;最大的在MAX中
let2: inc bx
loop let1
mov ah,4ch
int 21h
code ends
end start
(2)在 dos 子目录下保存为 6-7.asm,经过汇编 masm 6-7.asm,连接 link 6-7.obj,生成 6-7.exe。
D:\dos〉masm 6-7.asm
D:\dos〉link 6-7.obj
D:\dos〉6-7.exe
D:\dos〉debug 6-7.exe
(3)运行结果:执行后屏幕上没有显示结果。要观察运行结果,采用 DEBUG 执行 6-7.EXE。在 DEBUG 下,用 U 命令查看,找到断点,用 G 断点执行,再用 D DS:0
命令查看结果。
**注意
**:数据段中偏移地址为 0005H 单元是 MAX 单元,存放最大数 37H。
3.8 循环程序例八
本例子为教材的示例 6-8。
设计目标
在 X 字数组中查找 -1,找到后,将其删除,后续元素前移。并修改数组单元长度。
设计思路
(1)由于字数组的单元长度是数组元素个数的 2 倍,用右移一位获得元素个数;
(2)用串扫描指令查找 -1。找到时,DI 寄存器已经加 2,指向 -1 的下一个单元,因此可以直接将该元素及以后的元素前移,前移的次数由剩余的 CX 值决定;
(3)如果 -1 是最后的元素,则不用移动,直接将长度减 2;
实验步骤
(1)双击桌面上的记事本 gedit
,录入下列程序:
; 6-8.asm 在字数组X中查找-1,找到后将其删除,后续元素前移。
data segment
x dw 2,-4,-1,3,5,6,-8
n dw $-x ;数组单元长度
data ends
code segment
assume cs:code,ds:data,es:data
start: mov ax,data
mov ds,ax
mov es,ax ;附加段和数据段在一起
mov cx,n
shr cx,1 ;除以2,得到元素个数
mov ax,-1
mov di,offset x
cld
repne scasw ;在x中查找-1,不相等继续查找
je dele ;找到转dele
jmp out1 ;没找到则退出
dele: jcxz let1 ;-1是最后元素转out1
rept1: ;-1在中间,将后续元素前移
mov ax,x[di]
mov x[di-2],ax
add di,2
loop rept1
let1: sub n,2 ;数组长度减2
out1: mov ah,4ch
int 21h
code ends
end start
(2)在 dos 子目录下保存为 6-8.asm,经过汇编 masm 6-8.asm
,连接 link 6-8.obj
,生成 6-8.exe。
D:\dos〉masm 6-8.asm
D:\dos〉link 6-8.obj
D:\dos〉6-8.exe
D:\dos〉debug 6-8.exe
(3)运行结果:执行后屏幕上没有显示结果。要观察运行结果,采用 DEBUG 执行 6-8.EXE。在 DEBUG 下,用 U 命令查看,找到断点。先用 D DS:0
查看执行前的数据段,再用 G 断点执行,接着用 D DS:0
命令查看结果。
通过执行查找之前和查找之后的两次 D DS:0
命令,对比一下,可看出原来数组中的 FFFF(-1)被删除,数组长度也改变为 000CH(12)了。
**思考
**:为什么显示出的内存单元中有两个 FFF8(-8)?后面的 -8 属于数组元素吗?
3.9 循环程序例九
本例子为教材的示例 6-9。
设计目标
将 Y 字节数组分类为正数(Z1)和负数(Z2)两个数组.
设计思路
(1) 由于数组定义为字节单元,因此数组元素个数 N 可用当前单元地址 $ 和 Y 数组的首地址相减得到;
(2) 在循环中用分支指令判断正数和负数,正数、负数的个数分别用 SI 和 DI 表示。
实验步骤
(1)双击桌面上的记事本 gedit
,录入下列程序:
; 6-9.asm 将字节数组Y分为正数和负数两个数组。
data segment
y db 2,-4,-5,3,6,6,-8
n equ $-y ;数组长度
z1 db n dup(?)
z2 db n dup(?)
data ends
code segment
assume cs:code,ds:data
start: mov ax,data
mov ds,ax
mov cx,n
mov bx,0
mov si,0
mov di,0
rept1: mov al,y[bx] ;取出数组元素
cmp al,0 ;判断正负数
jle let1
mov z1[si],al ;正数数组
inc si
jmp let2
let1: mov z2[di],al ;负数数组
inc di
let2: inc bx ;下一个元素
loop rept1
mov ah,4ch
int 21h
code ends
end start
(2)在 dos 子目录下保存为 6-9.asm,经过汇编 masm 6-9.asm
,连接 link 6-9.obj
,生成 6-9.exe。
D:\dos〉masm 6-9.asm
D:\dos〉link 6-9.obj
D:\dos〉6-9.exe
D:\dos〉debug 6-9.exe
(3)程序执行:
- (a) 把程序调入 DEBUG,用 U 命令反汇编,显示出机器指令和对应的汇编指令
观察这个程序,在代码段的 0000~002BH 单元中存放。U 反汇编后,指令中的数据段名、变量名、符号地址、标号等均已变成了地址数据。
第 1 条指令 MOV AX,0B45
的机器码是 B8450B,共 3 个字节,其中,后两个字节 0B45 是程序中数据段名 DATA。其它指令的长度可以从它的机器码清楚地看出。又如程序中的指令 jle let1
变为 JLE 0021
,表示小于等于 0 转到 0021 单元的指令执行,而 0021 处的指令就是程序中标号 LET1 所在处的指令;再来看 LOOP 0011
,循环进入到 0011 处继续执行,而偏移地址 0011 即是标号 REPT1。
- (b)用 G 命令先执行到 0015 处,再用
D DS:0
命令查看数据段情况
数据段从 0 号单元开始存放数组 Y 的 7 个元素,正数、负数数组 Z1 和 Z2 都是 0。Y 数组的第 1 个元素 2 已经放入 AL 寄存器中。
- (c)接下来用单步调试 T 命令单步执行 2 次,并观察结果
t2 表示连续执行 2 条指令 CMP 和 JLE。比较的结果为正数,因此分支判断要执行 JLE 的下一条 MOV Z1[SI],AL
,将 AL 保存到正数数组 Z1 中。此时 Z1 数组由 [SI+0007]
表示,0007 是 Z1 数组的首地址,SI 的值为 0,即 Z1 数组的第 1 个元素在 Z1 的 0 号单元。在该行的右边,显示出 DS:0007=00
,由于本条指令还未执行,所以 Z1 数组的 0 号单元内容为 0,正数还未存入。随着程序的运行,有正数时,SI 要逐步加 1,正数元素可以不断地存入。
再连续执行 4 条,可看到 BX 已加 1,程序已经执行到 0B47:0026 处。此时可以用记事本打开源程序,对比一下,程序运行是否符合设计要求。
- (d)打开两个窗口
对照两个窗口的程序内容,程序已经跳转到“let2:”处,0026 即是标号 LET2。程序编写正确。如果有错误,可以随时修改源程序。
- (e)执行 LOOP 指令
在执行 LOOP 之前,CX=0007,用单步 t 命令执行后,CX 变为 0006,而且程序返回到 0B47:0011 处。再执行 t 之后,Y 数组的第 2 个数 -4(FCH)被放入 AL 中。连续执行 5 条指令后,又到 LOOP 指令。这时可以用 P 命令将循环一次执行完。
- (f)查看结果
P 命令之后,用 D DS:0
命令查看数据段存储单元。Y 数组中共有 7 个数据,其中有 4 个正数 2、3、6、6,存放在 0007 开始的单元中;3 个负数 -4、-5、-8,存放在 000E 开始的单元中。
**思考
**:如果再加上显示正、负数的个数,程序如何改?
3.10 循环程序例十
本例子为教材的示例 6-10。
**关注点
**:多重循环的用法
设计目标
在教材 6.5 节中,我们用单循环实现对一维数组求最大值,如果是二维数组求每行中的最大值,就要用双重循环实现。
查找 3×4 矩阵 A 每行中的最大值,并放入 MAX 矩阵。
设计思路
(1)外循环控制行数,内循环控制列数并完成最大值判断;
(2)内外循环都用 LOOP 指令,用堆栈保存外循环计数值 CX,从外循环进入内循环时要重置内循环的计数值 CX;
(3)用已存入一维矩阵 MAX 的数据与 A 矩阵的数据做比较,较大的数放入 MAX 后再与其它数继续比较。
实验步骤
(1)双击桌面上的记事本 gedit
,录入下列程序:
; 6-10.asm 查找3×4矩阵A每行中的最大值放入MAX矩阵
data segment
a db 2,-4,-5,10
db 3,6,-7,-12
db 14,-5,9,-3
m dw 3 ;3行
n dw 4 ;4列
max db 3 dup(0)
data ends
code segment
assume cs:code,ds:data
start: mov ax,data
mov ds,ax
mov cx,m ;共找3次,外循环次数
mov bx,0
mov si,0
rept2: push cx ;外循环次数入栈保存
mov cx,n ;内循环次数,4次
rept1: mov al,a[bx] ;找每行最大值
cmp al,max[si]
jle let2
mov max[si],al ;最大值放在max
let2: inc bx ;继续判断下一元素
loop rept1
inc si ;下一行
pop cx ;弹出外循环次数
loop rept2
mov ah,4ch
int 21h
code ends
end start
(2)在 dos 子目录下保存为 6-10.asm,经过汇编 masm 6-10.asm
,连接 link 6-10.obj
,生成 6-10.exe。
D:\dos〉masm 6-10.asm
D:\dos〉link 6-10.obj
D:\dos〉6-10.exe
D:\dos〉debug 6-10.exe
(3)运行结果:
**实验结果分析
**:
- (1)虽然 A 矩阵是二维的,但在存储器中实际存放时,是按顺序连续存放的。因此编写程序时,可以把 A 矩阵的数据用
MOV AL,A[BX]
相对寄存器寻址方式连续取出;此时 BX 的值可以加到 11,就把 A 矩阵的 12 个元素分别取出了。 - (2)从 0010H 单元开始是 MAX 矩阵,存放 3 个最大值。
- (3)行列值 M 和 N 定义为字型是与 CX 循环计数器类型相匹配。
4. 循环之循环
本节实验取自教材中第六章的《实例六 循环之循环》
实验目的
通过分析和运行示例程序,掌握循环程序设计思路和技巧。根据循环程序设计方法,尝试设计出各种风格的循环程序。
实验内容
参考示例 6-10、示例 6-9、示例 6-5(排序),完成下列实验内容:
- 分别统计 3 个班级中某科成绩优秀的人数和不及格的人数。提示:可以看成 3×N 二维数组。分别用 MAX 和 MIN 存放 90 分以上和 60 分以下的人数。
- 将上述题目改为用两个数组分别存放每班优秀的成绩和不及格的成绩。
- 分别对两组成绩按降序排序。
实验要求
- 3 个题目可任选 2 个
- 实验内容用截图形式记录实验结果
- 写出实验结果分析实验拓展
- 自己设计一个双重循环的题目并编程实现。
- 分析示例 6-8,如果改为在数组中查找某元素,然后在其后插入一个数据,程序怎么设计?