Skip to content
This repository was archived by the owner on May 8, 2019. It is now read-only.

Latest commit

 

History

History
614 lines (526 loc) · 21.7 KB

File metadata and controls

614 lines (526 loc) · 21.7 KB

第5部分 循环之循环

##一、课程简介

###1.1 课程说明

本课程基于《汇编语言(第2版)》郑晓薇 编著,机械工业出版社,可以配合该教材使用。本课程由郑晓薇授权制作,提取教材中的实例以及实验内容,可以在实验楼环境中完成所有实例及实验。实验课程制作符合教材原版实例驱动教学以及实验训练贯穿始终的特点。本教材可在当当网http://search.dangdang.com/?key=%BB%E3%B1%E0%D3%EF%D1%D4%A3%A8%B5%DA2%B0%E6%A3%A9%D6%A3%CF%FE%DE%B1 购买。

###1.2 实验环境

1.DOS 环境
实验环境中安装有dosemu可以模拟DOS环境,并提供DEBUGMASMLINK等汇编语言开发程序。如果您对实验环境不熟悉,推荐您先学习《新手入门之玩转实验楼》。
2.进入汇编实验
在桌面上双击dosemu图标,直接进入DOS。再做如下操作:

C:\〉D:           ——回车后进入D盘  
D:\〉CD DOS       ——进入DOS子目录  
D:\dos〉DIR       ——列出目录中的文件(包括masm和link)  

以后的汇编实验程序都要在dos这个子目录中进行。 img

##二、循环之循环
在汇编语言中,程序的循环可以用分支转移指令实现,也可以用8086指令系统中提供的专门的循环指令LOOP,这样使程序更清晰、简便。 除了循环指令之外,还有很多地方用到了循环的概念。例如串处理,需要对串中的字符循环地进行操作(示例6-2~示例6-4)。 ###2.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)运行结果:

img

示例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命令查看传送结果。

img

观察一下数据段的段地址和附加段的段地址是什么。用D命令查看数据是否已经传送到附加段了。

img

思考: 如果采用循环指令实现串传送,程序如何编写?

**示例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"。

**示例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是几号单元?
###2.2 排序 **示例6-5 ** 将字数组PART按升序排序。

  • 关注点:排序算法需要用双重循环

设计思路:
(1) 用两条LOOP指令实现双重循环时,对CX寄存器有冲突。采用PUSH CX指令将外循环的CX值入栈保存,内循环的LOOP结束后,再将外循环的CX恢复;
(2) 用寄存器相对寻址取出两数进行比较。
程序框图:
img

实验步骤:

(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。编程序,将它们按降序排序。
###2.3 多字节运算

示例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命令查看结果。

img
可以看出,0号~7号这8个字节单元是X,8号~F号8个单元是Y,10H号~17H号单元中为相加的结果Z。

示例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命令查看结果。

img

**注意:**数据段中偏移地址为0005H单元是MAX单元,存放最大数37H。

**示例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命令查看结果。

img

通过执行查找之前和查找之后的两次D DS:0 命令,对比一下,可看出原来数组中的FFFF(-1)被删除,数组长度也改变为000CH(12)了。

** 思考:**为什么显示出的内存单元中有两个FFF8(-8)?后面的-8属于数组元素吗?

##三、 循环之循环 本节实验取自教材中第六章的《实例六 循环之循环》

###3.1 循环的执行

示例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

程序执行:
(1) 把程序调入DEBUG,用U命令反汇编,显示出机器指令和对应的汇编指令
img 观察这个程序,在代码段的0000~002BH单元中存放。U反汇编后,指令中的数据段名、变量名、符号地址、标号等均已变成了地址数据。第1条指令MOV AX,0B45的机器码是B8450B,共3个字节,其中,后两个字节0B45是程序中数据段名DATA。其它指令的长度可以从它的机器码清楚地看出。又如程序中的指令jle let1变为JLE 0021,表示小于等于0转到0021单元的指令执行,而0021处的指令就是程序中标号LET1所在处的指令;再来看LOOP 0011,循环进入到0011处继续执行,而偏移地址0011即是标号REPT1。
(2)用G命令先执行到0015处,再用D DS:0命令查看数据段情况
img 数据段从0号单元开始存放数组Y的7个元素,正数、负数数组Z1和Z2都是0。Y数组的第1个元素2已经放入AL寄存器中。
(3)接下来用单步调试T命令单步执行2次,并观察结果
img
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,正数元素可以不断地存入。
img 再连续执行4条,可看到BX已加1,程序已经执行到0B47:0026处。此时可以用记事本打开源程序,对比一下,程序运行是否符合设计要求。
(4)打开两个窗口
img
对照两个窗口的程序内容,程序已经跳转到“let2:”处,0026即是标号LET2。程序编写正确。如果有错误,可以随时修改源程序。
(5)执行LOOP指令
img 在执行LOOP之前,CX=0007,用单步t命令执行后,CX变为0006,而且程序返回到0B47:0011处。再执行t之后,Y数组的第2个数-4(FCH)被放入AL中。连续执行5条指令后,又到LOOP指令。这时可以用P命令将循环一次执行完。
(6)查看结果
img P命令之后,用D DS:0命令查看数据段存储单元。Y数组中共有7个数据,其中有4个正数2、3、6、6,存放在0007开始的单元中;3个负数-4、-5、-8,存放在000E开始的单元中。

**思考:**如果再加上显示正、负数的个数,程序如何改?

###3.2 实验示例

  • **关注点:多重循环的用法 **

在教材6.5节中,我们用单循环实现对一维数组求最大值,如果是二维数组求每行中的最大值,就要用双重循环实现。
示例6-10 查找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)运行结果:
img

实验结果分析:

  • (1) 虽然A矩阵是二维的,但在存储器中实际存放时,是按顺序连续存放的。因此编写程序时,可以把A矩阵的数据用MOV AL,A[BX]相对寄存器寻址方式连续取出;此时BX的值可以加到11,就把A矩阵的12个元素分别取出了。
  • (2) 从0010H单元开始是MAX矩阵,存放3个最大值。
  • (3) 行列值M和N定义为字型是与CX循环计数器类型相匹配。

###3.3 实验任务 1.实验目的
通过分析和运行示例程序,掌握循环程序设计思路和技巧。根据循环程序设计方法,尝试设计出各种风格的循环程序。

2.实验内容
参考示例6-10、示例6-9、示例6-5(排序),完成下列实验内容:

  • 1) 分别统计3个班级中某科成绩优秀的人数和不及格的人数。 提示:可以看成3×N二维数组。分别用MAX和MIN存放90分以上和60分以下的人数。
  • 2) 将上述题目改为用两个数组分别存放每班优秀的成绩和不及格的成绩。
  • 3) 分别对两组成绩按降序排序。

3.实验要求

  • 1) 3个题目可任选2个
  • 2) 实验内容用截图形式记录实验结果
  • 3) 写出实验结果分析

4.实验拓展

  • 1) 自己设计一个双重循环的题目并编程实现。
  • 2) 分析示例6-8,如果改为在数组中查找某元素,然后在其后插入一个数据,程序怎么设计?