《汇编语言》第10章——实验10编写子程序

                                                  实验10 编写子程序
在这次实验中,我们将要编写3个子程序,通过它们来认识几个常见的问题和掌握解决这些问题的方法。同前面的所有实验一样,这个实验是必须独立完成的,在后面的课程中,将要用到这个实验中编写的3个子程序。
1.显示字符串
问题
显示字符串是现象工作中经常用到的功能,应该编写一个通用的子程序来实现这个功能。
我们应该提供灵活的调用接口,使调用者可以决定显示的位置(行、列)、内容和颜色。
子程序描述
名称:show_str
功能:在指定的位置,用指定的颜色,显示一个用0结束的字符串。
参数:(dh)=行号(取值范围0~24),(dl)=列号(取值范围0~79),
      (cl)=颜色,ds:si指向字符串的首地址
返回:无
应用举例:在屏幕的8行3列,用绿色显示data段中的字符串。
assume cs:code
data segment
    db 'Welcome to masm!', 0
data ends
code segment
start:    mov dh, 8
        mov dl, 3
        mov cl, 2
        mov ax, data
        mov ds, ax
        mov si, 0
        call show_str
        
        mov ax, 4c00h
        int 21h
show_str:    ;
            ;
            ;
code ends
end start
提示
(1)子程序的入口参数是屏幕上的行号和列号,注意在子程序内部要将它们转化为显存中的地址,
首先要分析一下屏幕上的行列位置和显存地址的对应关系 ;
(2)注意保存子程序中用到的相关寄存器;
(3)这个子程序的内部处理和显存的结构密切相关,但是向外提供了与显存结构无关的接口。
通过调用这个子程序,进行字符串的显示时可以不必了解显存的结构,为编程提供了方便。在实验中,
注意体会这种设计思想。

;t10_sy1.asm
assume cs:code 
data segment
	db 'Welcome to masm!',0
data ends

code segment 
start:		mov dh, 8
			mov dl, 3
			mov cl, 2
			mov ax, data 
			mov ds, ax 
			mov si, 0
			call show_str
		
			mov ax, 4c00h
			int 21h
		
show_str:	push ax 
			push bx
			push es
			push si 
			mov ax, 0b800h
			mov es, ax
			mov  ax, 160
			mul dh 
			mov bx, ax		;bx=160*dh 
			mov ax, 2
			mul dl			;ax=dl*2
			add bx, ax		;mov bx, (160*dh + dl*2)设置es:bx指向显存首地址
			mov al, cl 		;把颜色cl赋值al
			mov cl, 0

show0:		mov ch, [si]
			jcxz show1		;(ds:si)=0时,转到show1执行
			mov es:[bx], ch
			mov es:[bx].1, al
			inc si			;ds:si指向下一个字符地址
			add bx, 2		;es:bx指向下一个显存地址
			jmp show0
			
show1:		pop si
			pop es
			pop bx 
			pop ax
			ret
code ends
end start

debug调试:

 

 

 .........


2.解决险法溢出的问题
问题
前面讲过,div指令可以做除法。当进行8位除法的时候,用al存储结果的商,ah存储结果的余数;
进行16位险法的时候,用ax存储结果的商,dx存储结果的余数。可是,现在有一个问题,
如果结果的商大于al或ax所能存储的最大值,那么将如何?
比如,下面的程序段:
mov bh, 1
mov ax, 1000
div bh
进行的是8位除法,结果的商为1000,而1000在al中放不下。
又比如,下面的程序段:
mov ax, 1000H
mov dx, 1
mov bx, 1
div bx
进行的是16位除法,结果的商为11000H,而11000H在ax中存放不下。
我们在用div指令做除法的时候,很可能发生上面的情况:结果的商过大,超过了寄存器所能存储的范围。
当CPU执行div等除法指令的时候,如果发生这样的情况,将引发CPU的一个内部错误,这个错误称为:
除法溢出。我们可以通过特殊的程序来处理这个错误,但在这里我们不讨论这个错误的处理,这是后面的
课程中要涉及的内容。下面我们仅仅来看一下除法溢出发生时的一些现象,如图10.1所示。


图中展示了在Windows 2000中使用debug执行相关程序段的结果,div指令引发了CPU的除法溢出,
系统对其进行了相关的处理。
好了,我们已经清楚了问题的所在:用div指令做除法的时候可能产生除法溢出。
由于有这样的问题,在进行除法运算的时候要注意除数和被除数的值,比如1000000/10就不能用div指令来计算。
那么怎么办呢?我们用下面的子程序divdw解决。
子程序描述
名称:divdw
功能:进行不会产生溢出的除法运算,被除数为dword型,除数为word型,结果为dword型。
参数:(ax)=dword型数据的低16位
      (dx)=dword型数据的高16位
      (cx)=除数
返回:(dx)=结果的高16位,(ax)=结果的低16位
      (cx)=余数
应用举例:计算1000000/10 (F4240H/0AH)
mov ax, 4240H
mov dx, 000FH
mov cx, 0AH
call divdw
结果:(dx)=0001H,(ax)=86A0H,(cx)=0
提示
给出一个公式:
X:被除数,范围:[0,FFFFFFFF]
N:除数,范围:[0,FFFF]
H:X高16位,范围:[0,FFFF]
L:X低16位,范围:[0,FFFF]
int():描述性运算符,取商,比如,int(38/10)=3
rem():描述性运行符,取余数,比如,rem(38/10)=8
公式:X/N=int(H/N)*65536+[rem(H/N)*65536+L]/N
这个公式将可能产生溢出的除法运算:X/N,转变为多个不会产生溢出的除法运算。
公式中,等号右边的所有除法运算都可以用div指令来做,肯定不会导致除法溢出。
(关于这个公式的推导,有兴趣的读者请参看附注5.)

;t10_sy2.asm
;应用举例:计算1000000/10(f4240h/0ah)

assume cs:code 
code segment 
start:	mov ax,4240h 			;被除数低16位
		mov dx,000fh 			;被除数高16位
		mov cx, 0ah 			;除数
		call divdw 
		
		mov ax,4c00h 
		int 21h 
divdw:	push si 
		push bx 
		push ax 				;保存低16到栈中
		mov ax, dx 				;把高16位dx赋给ax  相当于先计算dx/cx
		mov dx,0				;dx为0
		div cx 					;被除数的高位/cx,高位在ax,余数在dx
		mov si,ax 
		pop ax 
		div cx 					;(被除数高位的商+低位)/cx,高位在ax,余数在dx
		mov cx,dx 				;余数入cx
		mov dx,si 				;高位的商入dx
		pop bx 
		pop si 
		ret 
code ends 
end start 


3.数值显示
问题
编程,将data段中的数据以十进制的形式显示出来。
data segment
    dw 123, 12666, 1, 8, 3, 38
data ends
这些数据在内存中都是二进制信息,标记了数值的大小。要把它们显示到屏幕上,成为我们能够
读懂的信息,需要进行信息的转化。比如,数据12666,在机器中存储为二进制信息:
0011000101111010B(317AH),计算机可以理解它。而要在显示器上读到可以理解的数值12666,
我们看到的应该是一串字符:”12666“。由于显卡遵循的是ASCII编码,为了让我们能在显示器
上看到这串字符,它在机器中应以ASCII码的形式存储为:31H、32H、36H、36H、36H(字符
”0“~”9“对应的ASCII码为30H~39H)。
通过上面的分析可以看到,在概念世界中,有一个抽象的数据12666,它表示了一个数值的大小。
在现实世界中它可以有多种表示形式,可以在电子机器中以高低电平(二进制)的形式存储,
也可以在纸上,黑板上、屏幕上以人数的语言”12666“来书写。现在,我们面临的问题就是,
要将同一抽象的数据,从一种表示形式转化为另一种表示形式。
可见,要将数据用十进制形式显示到屏幕上,要进地两步工作:
(1)将用二进制信息存储的数据转变为十进制形式的字符串;
(2)显示十进制形式的字符串。
第二步我们在本次实险的第一个子程序中已经实现,在这里只要调用一下show_str即可。我们来㢑第一步,
因为将二进制信息转变为十进制形式的字符串也是经常要用到的功能,我们应该为它编写一个通用的子程序。
子程序描述
名称:dtoc
功能:将word型数据转变为表示十进制的字符串,字符串以0为结尾符。
参数:(ax)=word型数据
      ds:si指向字符串的首地址
返回:无
应用举例:编程,将数据12666以十进制的形式在屏幕的8行3列,用绿色显示出来。在显示
时我们调用本次实验中的第一个子程序show_str。
assume cs:code
data segment
    db 10 dup (0)
data ends
code segment
 start:    mov ax, 12666
        mov bx, data
        mov ds, bx
        mov si, 0
        call dtoc
        
        mov dh, 8
        mov dl, 3
        mov cl, 2
        call show_str
        :
        :
        :
code ends
end start
提示
下面我们对这个问题进行一下简单地分析。
(1)要得到字符串”12666“,就是要得到一列表示该字符串的ASCII码:31H、32H、36H、36H、36H。
十进制数码字会对应的ASCII码=十进制数据码值+30H。
要得到表示十进制数的字符串,先求十进制数每位的值。
例:对于12666,先求得每位的值:1、2、6、6、6。再将这些数分别加上30H,便得到了表示12666的
ASCII码串:31H、32H、36H、36H、36H。
(2)那么,怎样得到每位的值呢?采用下面的方法:

可见,用10除12666,共除5次,记下每次的余数,就得到了每位的值。
(3)综合以上分析,可得出处理过程如下。
用12666除以10,循环5次,记下每次的余数:将每次的余数分别加30H,便得到了表示
十进制数的ASCII码串。如下:

;t10_sy3.asm
;问题:编程,将data段中的数据以十进制的形式显示出来。 db:字节型数据  dw:字型数据  dd:双字型数据
assume cs:code,ds:data 
data segment 
	db 10 dup (0)
data ends 
code segment 
start:	mov ax,12666
		mov bx,data 
		mov ds,bx 
		mov si,0			;ds:si指向data首地址
		call dtoc1
		
		mov dh,8
		mov dl,3
		mov cl,2 
		call show_str 
		
		mov ax,4c00h 
		int 21h 


;名称:dtoc1
;功能:将word型数据转变为表示十进制的字符串
;参数:(ax)=word型数据;
;ds:si指向字符串首地址
;返回:无。
dtoc1:	push ax 
		push bx 
		push cx 
		push dx 
		push si 
		push di 
		mov di,0 
d10:	mov dx,0		;设置被除数高位为0
		mov bx,10		;除数为10 
		div bx 
		add dx,30h 		;ax/10的余数+30H,转为字符
		push dx			;字符入栈
		inc di 			;记录字符个数
		mov cx,ax 
		jcxz dll 		;当ax/10的商=0时,转到dll执行
		jmp d10 
dll:	mov cx,di 
d12:	pop dx 			;字符出栈
		mov [si],dl 
		inc si 			;ds:si指向下一单元 
		loop d12 
		mov dl,0 
		mov [si],dl 	;设置结尾符0 
		pop si 
		pop di 
		pop dx 
		pop cx 
		pop bx 
		pop ax 
		ret
;名称:show_str
;功能:在指定的位置,用指定的颜色,显示一个用0结束的字符串。
;参数:(dh)=行号(取值范围0~24);
;      (dl)=列号(取值范围0~79);
;      (cl)=颜色;
;      ds:si指向字符串的首地址。
;返回:无。
show_str:	push ax 
			push bx 
			mov ax,0b800h 
			mov es,ax 
			mov ax,160
			mul dh 
			mov bx,ax 			;bx=160*dh 
			mov ax,2 
			mul dl 				;ax=dl*2 
			add bx,ax 			;mov bx,(160*dh+dl*2)设置es:bx指向显存首地址
			mov al,cl			;把颜色cl赋值al 
			mov cl,0 
show0:		mov ch,[si] 
			jcxz show1			;(ds:si)=0时,转到show1执行
			mov es:[bx],ch 
			mov es:[bx+1],al 
			inc si 				;ds:si指向下一个字符地址
			add bx,2			;es:bx指向下一个显存地址
			jmp show0 
show1:		pop bx 
			pop ax 
			ret 
code ends 
end start 
		





 运行结果:

(4)对(3)的质疑
在已知数据是126666的情况下,知道进行5次循环。可在实际问题中,数据的值是多少程序员并不知道,
也就是说,程序员不能事先确定循环次数。
那么,如何确定数据各位的值已经全部求出了呢?我们可以看出,只要是除到商为0,
各位的值就已经全部求出。可以使用jcxz指令来实现相关的功能。

;t10_sy3_4.asm
;问题:应用举例:将数据2494967295以十进制的形式在屏幕的8行3列,用绿色显示出来。
assume cs:code,ds:data 
data segment 
	db 10 dup (0)
data ends 
code segment 
start:	mov ax,0ffffh
		mov dx,0ffffh 
		mov bx,data
		mov ds,bx 
		mov si,0	;ds:si指向data首地址
		call dtoc2
		
		mov dh,8
		mov dl,3
		mov cl,2
		call show_str 
		
		mov ax, 4c00h 
		int 21h


;名称:dtoc2
;功能:将word型数据转变为表示十进制的字符串,字符串以0为结尾符
;参数:(ax)=dword型数据的低16位;
;	   (dx)=dword型数据的高16位
;		ds:si指向字符串首地址
;返回:无。
dtoc2:
		push ax 
		push bx 
		push cx 
		push dx 
		push si 
		push di 
		mov di,0

d20:	mov cx,10		;除数为10 
		call divdw 
		add cx,30h		;余数+30h,转为字符 
		push cx			;字符入栈 
		inc di 			;记录字符个数 
		mov cx,ax 
		jcxz d21		;低位商=0时,转到d21检测高位商
		jmp d20 
		
d21:	mov cx,dx 
		jcxz d22		;高低位商全=0时,转到d22执行
		jmp d20 
		
d22:	mov cx,di 
d23:	pop ax 			;字符出栈 
		mov [si],al 
		inc si 			;ds:si指向下一单元
		loop d23 
		mov al,0 
		mov [si],al		;设置结尾符0
		pop di 
		pop si 
		pop dx 
		pop cx 
		pop bx 
		pop ax 
		ret 


;名称:show_str
;功能:在指定的位置,用指定的颜色,显示一个用0结束的字符串。
;参数:(dh)=行号(取值范围0~24);
;      (dl)=列号(取值范围0~79);
;      (cl)=颜色;
;      ds:si指向字符串的首地址。
;返回:无。
show_str:	push ax 
			push bx 
			mov ax,0b800h 
			mov es,ax 
			mov ax,160
			mul dh 
			mov bx, ax			;bx=160*dh
			mov ax,2
			mul dl				;ax=dl*2 
			add bx,ax 			;mov bx,(160*dh+dl*2)设置es:bx指向显存首地址
			mov al,cl			;把颜色cl赋值al
			mov cl,0

show0:		mov ch,[si]
			jcxz show1			;(ds:si)=0时,转到show1执行
			mov es:[bx],ch 
			mov es:[bx+1],al 
			inc si 				;ds:si指向下一个字符地址
			add bx,2			;es:bx指向下一个显存地址
			jmp show0
			
show1:		pop bx 
			pop ax 
			ret 
			
;名称:divdw
;功能:进行不会产生溢出的除法运算,被除数为dword型,除数为word型,结果为dword型。
;参数:(ax)=dword型数据的低16位;
;      (dx)=dword型数据的高16位;
;      (cx)=除数。
;返回:(dx)=结果的高16位;
;      (ax)=结果的低16位;
;      (cx)=余数。

divdw:		push si 
			push bx 
			push ax 
			mov ax,dx 
			mov dx,0
			div cx 				;被除数的高位/cx
			mov si,ax 
			pop ax 
			div cx 				;(被除数高位的商+低位)/cx
			mov cx,dx 			;余数入cx
			mov dx,si			;高位的商入dx
			pop bx 
			pop si 
			ret 

code ends 
end start 
	
	

运行结果:

  • 6
    点赞
  • 31
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值