汇编语言随笔(8)-实验9(显示字符串)、实验10(子程序:除法溢出,数值到字符串的转换)和课程设计1

实验9

      编程,在屏幕中间分别显示绿色,绿底红色,白底蓝色的字符串‘welcome to masm!’。
      80x25彩色字符模式下的显示缓冲区,每页可以显示25行,每行80个字符,其中每个字符占两个字节的存储空间,低位字节存储字符的ASCII值,高位字节存储字符的属性。所以一行总共有160个字节。其中属性字节的格式如下:

7 6 5 4 3 2 1 0
含义 BL R G B I R G B

      其中BL为闪烁,R为红色,G为绿色,B为蓝色,I为高亮,在高位的RGB表示背景颜色,低位的表示前景颜色。
      以绿底红色为例,它的颜色属性为00100100B。显示缓冲区的段地址为0b800h,默认在第0页的屏幕中间的偏移地址为12x160+(40-16/2)x2。其中16为本程序中字符串的字节数。搞清楚了目的地址和颜色属性之后,我们开始编写程序:

	assume cs:code
	data segment
		db 'welcome to masm!'
	data ends
	code segment
  start:mov ax,0b800h
  		mov es,ax
  		mov di,12*160+32*2		目的地址存储在es:di中
  		mov ax,data
  		mov ds,ax
  		mov bx,0
  		mov cx,10h
  		
  	 s: mov al,[bx]
  	 	mov es:[di],al
  	 	mov byte ptr es:[di+1],00100100B
  	 	inc bx
  	 	add di,2
  	 	loop s
  	 	
  	 	mov ax,4c00h
  	 	int 21h
  	 code ends
  	 end start
  		

实验10

问题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: push cx
		  push dx
		  push ax
		  push si
		  push ds
		  push di
		  push es

		  mov ax,0b800h
		  mov es,ax
		  mov di,dh*160+dl*2	es:di为目的地址
	   s: mov al,[si]
		  cmp al,0
		  je done
		  mov es:[di],al
		  mov es:[di+1],cl
		  add di,2
		  inc si
		  jmp short s
			
	done: pop es
		  pop di
		  pop ds
		  pop si
		  pop ax
		  pop dx
		  pop cs
		  ret

	code ends
	end start
问题2 解决除法溢出

      问题:除法溢出指的是商过大,超出了寄存器的存储范围。比如:16位的被除数和8位的除数在做除法的时候,用al存储结果的商,ah存储结果的余数。如果当除数为1时,那么商也是一个16位数据,这超过了al这个8位寄存器所能表示数据的范围了。
      子程序描述如下:
            名称:divdw
            功能:进行不会溢出的除法运算,被除数为dword型,除数为word型,结果为dword型。
            参数:(ax)=dword型数据的低16位,(dx)=dword型数据的高16位,(cx)=除数
            返回:(dx)=结果的高16位,(ax)=结果的低16位,(cx)=余数
      提示:参考公式如下:
            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
            注意:乘以65536相当于左移16位。
      计算1000000/10(F4240H/0AH)完整程序如下:

	assume cs:code
	data segment
		dw 4 dup(0)
	data ends
	code segment
 start: mov ax,data
 		mov ds,ax
 		mov si,0
 		mov ax,4240h
 		mov dx,0fh
 		mov cx,0ah
		call divdw
		
		mov ax,4c00h
		int 21h


   divdw: push ds
		  push si
		  push cx
		  push ax
		  push dx
		  
		  mov [si],ax		保存被除数的低16位L
		  mov [si+2],dx		保存被除数的高16位H
		  mov ax,dx
		  mov dx,0
		  div cx			用高16位H除以除数N
		  mov [si+4],ax		保存商
		  mov ax,[si]
		  div cx			rem(H/N)作为高16位,L作为低16位,除以N
		  mov [si+2],ax
		  mov [si],dx
		  //将商的高16位存储在[si+4]处,低16位存储在[si+2]处,余数存储在[si]处。
		  

	done: pop dx
		  pop ax
		  pop cx
		  mov cx,[si]			保存返回值到相应的寄存器中
	      mov ax,[si+2]
		  mov dx,[si+4]
		  pop si
		  pop ds
		  ret

	code ends
	end start
问题3 数值(int)到字符串形式的转换

      问题:把数据用十进制的形式显示到屏幕上,需进行两步的操作:(1),将用二进制信息存储的数据转变为十进制形式的字符串(2),显示十进制形式的字符串(只需调用问题1的show_str即可)
      子程序描述如下:
            名称:dtoc
            功能:将word型数据转变为十进制数的字符串,字符串以0为结尾符。
            参数:(ax)=word型数据、ds:si指向字符串的首地址
            返回:无
      编程,将数据12666以十进制形式在屏幕的8行3列,用绿色显示出来。在显示时调用本次实验中的第一个子程序show_str。
      思路:想要得到十进制数12666的每一位数值,需将12666除以10,它的余数6为个位上的数值,它的商1266作为新的被除数再除以10,余数6为原数据十位上的数值,它的商再次除以10,以此类推,进行5次除以10操作就可以得到每一位的值了。
      在已知数据为12666的情况下,循环次数为5,但对于数据未知的情况下,就需要判定每次得到的商是否为0了,当除到商为0时,所有位上的值就被全部求出。在此采用jcxz指令来实现此功能。
      除法功能在此采用32位除以16位的方式来实现,因为这样不会导致溢出。因为如果采用16位除以8位的方式的话,那么它的商也是用8位来保存的,而12666/10=1266,这已经大于8位数据的最大值255了。

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		
		
		mov ax,4c00h
		int 21h

    dtoc: push bx
		  push cx
		  push ax
		  push dx
		  
		  mov bx,10		整数10作为16位的除数放在bx中
		  mov si,9
		  mov byte ptr [si],0	字符串末尾置0
		  sub si,1
	   s: mov dx,0
		  div bx
		  add dl,'0'	容易看出dh中全为0,计算出数据的实际ASCII值
		  mov [si],dl	字节单元数据的传送,将余数保存到内存中
		  mov cx,ax
		  jcxz done
		  sub si,1
		  jmp short s

	done: pop dx
		  pop ax
		  pop cx
		  pop bx
		  ret

	code ends
	end start
课程设计1

      将实验七中的公司21年的数据在屏幕上显示出来,一共占据屏幕21行,每一行中显示出每一年的数据,依次为年份、收入、雇员数和平均收入。
      思路:
            先计算出每一字段的行号和列号,可得,行号范围为[2,22];每一行第一个字段起始地址为6,第二个字段起始地址为28,第三个字段起始地址为50,最后一个字段起始地址为72。这样就可以将所有数据内容显示在屏幕中央。
            说明一点,前面已经实现了的子程序在此将被直接调用。

            但还需要编写一个将dword型数据转换为字符串的子程序。说明如下:
                  名称:ddtoc(为了和word型数据转换子程序dtoc区分开来)
                  功能:将dword型数据转变为表示十进制数的字符串,字符串以0为结尾符
                  参数:(ax)=dword型数据的低16位,(dx)=dword型数据的高16位,ds:si指向字符串的首地址
                  返回:无
                  仍需要注意除法溢出的问题。
      完整程序如下:

assume cs:code
	data segament
		db '1975','1976','1977','1978','1979','1980','1981','1982','1983'
		db '1984','1985','1986','1987','1988','1989','1990','1991','1992'
		db '1993','1994','1995'
		//用21个字符串来表示年份
		dd 16,22,382,1356,2390,8000,16000,24486,50065,97479,140417,197514
		dd 245980,590827,803530,1183000,1843000,2759000,3753000,4649000,5937000
		//用21个双字型数据来表示公司每年的总收入
		dw 3,7,9,13,28,38,130,220,476,778,100,1442,2258,2793,4037,5635,8226
		dw 11542,14430,15257,17800
		//用21个字型数据来表示公司每年的员工数量
	data ends
	
	buf segment
		db 16 dup(0)	这块内存是用来进行双字型到字符串转换的
	buf ends

	code segment
  start:mov ax,data
  		mov ds,ax
  		mov bx,0		ds:bx表示数据段起始的偏移地址
  	 	mov ax,0b800h
  	 	mov es,ax		
  	 	mov di,160*2		es:di指向显示缓冲区第二行第0列
  		
  		mov al,21
  		mov dl,8
  		mul dl
  		mov si,ax		计算21*8,保存到si,这是雇员数在数据段的起始偏移地址
  		
  		mov cx,21		外层循环次数21次
  		
  body: mov ax,[bx]
  	 	mov es:[di+6],ax
  	 	mov ax,[bx+2]
  	 	mov es:[di+8],ax		年份
  	 	
  	 	push si
  	 	push ds
  	 	mov ax,[bx+84]
  	 	mov dx,[bx+86]
  	 	call ddtoc
  	 	mov bp,0
  subb: mov al,[si]
  	 	cmp al,0
  	 	je next
  	 	mov es:[di+bp+28],al
  	 	inc bp
  	 	inc si
  	 	jmp short subb			总收入
  	 	
  next:	pop ds
  		pop si
  		mov ax,[si]
  		push ds
  		push si
  		mov ax,buf
  		mov ds,ax
  		call dtoc
  		mov bp,0
 subbo: mov al,[si]
 		cmp al,0
 		je nnext
 		mov es:[di+bp+50],al
 		inc bp
 		inc si
 		jmp short subbo			雇员数		
  	 	
 nnext: pop si
 		pop ds
 		mov dx,[bx+86]
  	 	mov ax,[bx+84]
  	 	div word ptr [si]		计算人均收入
  	 	push ds
  	 	push si
  	 	mov ax,buf
  	 	mov ds,ax
  	 	call dtoc				字型数据转换为字符串
  	 	mov bp,0
subbody:mov al,[si]
		cmp al,0
		je nnnext
		mov es:[di+bp+72],al
		inc bp
		inc si
		jmp short subbody	
  	 	
nnnext: pop si
		pop ds
		add di,160
  	 	add si,2
  	 	add bx,4
  	 	loop body
  		mov ax,4c00h
  		int 21h

 ddtoc: push cx
 		push ax
 		push dx
 		
 		mov ax,buf
 		mov ds,ax
 		mov si,15
 		mov byte ptr [si],0
 		dec si
 	 s: mov cx,10		除数10保存在cx中
 	 
 	 	push si
 	 	mov si,0		将ds:si开始后的6个字节作为divdw中的临时缓存
 		call divdw		divdw采用本文中实现的子程序
 		pop si
 		
 		add cl,'0'
 		mov [si],cl
 		dec si
 		cmp dx,0
 		jne s
 		cmp ax,0
 		je done
 		jmp short s

  done: inc si			注意,最后退出时si并不是刚好指向字符串首地址
  		pop dx
  		pop ax
  		pop cx
  		ret
 		
  	code ends
 	end start

Guess you like

Origin blog.csdn.net/Little_ant_/article/details/108227058