王爽《汇编语言》第三版 第十章 实验十

友链

1、显示字符串

代码如下:

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 cx
				push dx
				push es
				push di
				push si
				;根据上节中的框架,为了不让子程序干扰主程序中寄存器的值,将所有子程序会用到的寄存器进行压栈

				mov ax, 0b800h
				mov es, ax
				;颜色区的段地址
				mov al, 160 
				mul dh
				;每行占160个字节,乘以行数
				push ax
				;将行计算的结果存储到栈中
				mov al, 2
				mul dl
				;每列占2个字节,乘以列数
				pop bx
				;将上次运算的结果(160×行数)的值转移到bx中
				add bx, ax	;此时的ax值为(2×列数)
				;将两者相加,最终结果保存到bx中
				mov dl, cl
				;因为下面的跳转指令jcxz需要用到cx寄存器,故需要将cl的值先保存在dl中
				mov di, 0
	change:		mov cl, ds:[di]
				mov ch, 0
				jcxz ok
				mov ch, dl 
				mov es:[bx+si], cx
				add si, 2 
				inc di
				jmp short change

	ok:			pop si
				pop di
				pop es
				pop dx
				pop cx
				pop bx
				pop ax
				ret

	code ends
	end start

运行结果

王爽《汇编语言》第三版 第十章 实验十_第1张图片
在8行3列处开始显示绿色字符串

2、解决除法溢出的问题

代码如下:

assume cs:code

data segment
	dw 16 dup(0)
	;此数据段用来临时存放数据
data ends

code segment
start:	mov ax, 4240h
		mov dx, 000fh
		mov cx, 0ah
		mov bx, data
		mov ds, bx
		call divdw
		mov ax, ds:[0]
		mov dx, ds:[2]
		mov cx, ds:[4]

		mov ax, 4c00h
		int 21h

divdw:	push ds
		push dx
		push cx
		push ax 

		mov ax, dx
		mov dx, 0
		div cx
		;ax存放商,dx存放余数
		;根据公式,使用被除数高位除以除数得到的商×65536
		;*65536等价于在低位加16个0,因此操作就会变得非常简单
		;使用被除数高位除以除数得到的余数×65536+被除数的低位,再将得到的结果除以除数
		;两者的结果相加,即可得到32位/16位的无溢出结果
		push dx
		;使用栈临时保存余数
		mov dx, ax
		mov ax, 0
		mov ds:[0], ax
		mov ds:[2], dx
		pop dx
		;弹出余数,作为右操作数中被除数的高16位 
		pop ax
		;得到被除数的低16位
		push ax
		;恢复栈顶数据,避免对主程序造成干扰 
		;将右操作数[]中的左操作数的低16位和被除数的低16位相加
		;但是右操作数[]中的左操作数的低16位一定是全0的,因此我们可以省略这一步
		;直接执行pop ax,将被除数的低16位作为右操作数的低16位
		div cx
		;ax存放商,dx存放余数
		;由于左操作数的低16位一定是全0,所以不必与其相加,直接将
		;右操作数的低16位存储到ds:[0]内存单元即可
		mov ds:[0], ax
		;商的低16位放到ds:[0]单元中
		mov ds:[4], dx  
		;余数放到ds:[4]单元中
		;ds:[2]中一直保存的都是商的高16位,且没有被更改过,因此无须任何操作

		pop ax
		pop cx
		pop dx  
		pop ds
		ret
code ends
end start

运行结果
这里写图片描述
与书中结果一致

3、数值显示

无注释版:

assume cs:code

data segment
	dw 123, 12666, 1, 8, 3, 38
data ends

ascii segment
	db 100 dup(0) 
ascii ends

code segment
start:		mov bx, data
			mov ds, bx
			mov si, 0
			call dtoc

			mov cx, 6
			mov ax, 0
			mov dh, 8
show:		push cx
			mov dl, 3
			mov cl, 2
			call show_str
			pop cx
			inc dh
			loop show
			mov ax, 4c00h
			int 21h 

dtoc:		push ax
			push bx
			push cx
			push dx
			push ds
			push si
			push es

			mov ax, ascii
			mov es, ax
			mov di, 0
			mov cx, 6

loop_zone:	push cx
			mov dx, 0
			mov ax, ds:[si]

split:		push dx
			mov cx, 0ah
			mov dx, 0
			div cx
			mov cx, dx

			pop dx
			push cx
			inc dx
			mov cx, ax
			add cx, dx
			jcxz ok1
			jmp short split 

ok1:		pop ax
			add al, 30h
			mov byte ptr es:[di], al
			inc di
			dec dx
			mov cx, dx
			jcxz last
			jmp short ok1

last:		mov ah, 0
			mov byte ptr es:[di], ah 
			inc di 
			pop cx
			add si, 2
			loop loop_zone

			pop es
			pop si
			pop ds
			pop dx
			pop bx
			pop cx
			pop ax
			ret

show_str:	push bx
			push cx
			push dx
			push ds
			push es
			push di
			push ax
			push si 

			mov di, ax
			mov ax, 0b800h
			mov es, ax 
			mov ax, ascii
			mov ds, ax 
			mov al, 160 
			mul dh 
			push ax 
			mov al, 2
			mul dl 
			pop bx 
			add bx, ax 
			mov dl, cl 

change:		mov cl, ds:[di]
			mov ch, 0 
			inc di 
			jcxz ok2
			mov ch, dl 
			mov es:[bx+si], cx
			add si, 2
			jmp short change

ok2:		pop si
			pop ax
			mov ax, di
			pop di
			pop es
			pop ds
			pop dx
			pop cx
			pop bx 
			ret
code ends
end start 

详解版

assume cs:code

	data segment
		dw 123, 12666, 1, 8, 3, 38
	data ends

	ascii segment
		db 100 dup(0)
		;ascii码值,一个字节即可存储
	ascii ends

	div segment
		dw 16 dup(0)
		;除法溢出计算需要使用该数据段来临时保存结果
	div ends

	code segment
	start:		mov bx, data
				mov ds, bx
				;ds段寄存器用来存放待处理的数据
				mov si, 0
				call dtoc

				mov cx, 6
				mov ax, 0
				mov dh, 8
	show:		push cx
				mov dl, 3
				mov cl, 2
				call show_str
				pop cx
				;我们需要更改行号来避免覆盖
				inc dh
				loop show
				mov ax, 4c00h
				int 21h 

	dtoc:		;该子程序用于将数值型的数字转换为字符串
				;十进制数值转换为ASCII码值,转换关系为:ascii=10进制+30H
				;要想将一个十进制的整数拆分成一个一个的数值,那我们需要让这个数
				;除以10,然后将得到的结果依次入栈,除完之后再依次出栈,即可得到由高位到低位
				;的所有数值,之后将这些值加上30H,即得到其对应的ASCII码值,然后将这些
				;ASCII码值存放到一个数据段中,调用show_str函数,来在屏幕上显示这些数值
				;为了存储转换后的ASCII码值,我们需要新开辟一个数据段
				push ax
				push bx
				push cx
				push dx
				push ds
				push si
				push es

				mov ax, ascii
				mov es, ax
				;使用es段寄存器来存储转换后的ascii码值
				mov di, 0
				;存储ASCII数据时用来指向ascii段中的每个内存单元 
				mov cx, 6
	loop_zone:	push cx
				;因为内层循环会更改cx的值,所以我们需要使用栈结构来保存cx的值
				mov dx, 0
				;记录十进制数据的位数
				mov ax, ds:[si]
				;ax存放被除数
	split:		push dx
				;下面要用到dx寄存器,因此我们先保存dx
				mov cx, 0ah
				;cx存放除数 
				mov dx, 0
				;dx作为被除数高16位,置0
				div cx
				;32/16的除法运算,商存储在ax中,余数存储在dx中
				mov cx, dx
				;call divdw
				;其实用不着调用divdw,这个除法溢出问题不是真正的除法溢出问题
				;我们只需要将被除数凑成32位的,除数当做16位的即可
				;此程序返回运算后的商和余数,分别保存在ax和cx中

				;如果被除数大于2550,al是无法存放商的,会造成溢出,因此,我们需要调用本实验中第二个函数
				;专门用于解决除法溢出问题的函数,虽然程序2解决的是32/16的除法运算的溢出问题,但是对于16/8位的
				;除法运算也是完全适用的

				;由于入栈时只能使用字型数据,所以我们压入的是ax,此时需要将
				;无关数据,也就是al置0
				pop dx
				;取出dx更改前的值
				push cx
				;余数入栈
				inc dx
				;当循环终止的时候可以进行弹栈存储操作了,但是我们需要一个标记,来标识我们需要
				;弹出多少次,我们使用dx来进行存储
				mov cx, ax
				add cx, dx
				;ax中的值在下一次运算中一定会用到,dx中的值也有可能会用到(当被除数很大时)
				;此时可以临时保存数据的只有cx了,因此我们直接将运算结果放到cx中
				;一举两得
				jcxz ok1
				;处理过程是需要循环的,循环结束的条件是商==0 
				;我们只需要将执行jcxz指令即可,当cx的值位0的时候,它会自动跳转到ok1循环的
				jmp short split 

	ok1:		pop ax
				add al, 30h
				mov byte ptr es:[di], al
				inc di
				dec dx
				mov cx, dx
				jcxz last
				jmp short ok1

	last:		;最后一步,在数据的ASCII数据形式的最后加上一个0
				mov ah, 0
				mov byte ptr es:[di], ah 
				inc di
				;从split到ok1到最后一步是对data段中第一个数据的处理
				;这个过程需要进行循环操作
				;在这个循环中,di,bx是放在循环外的
				pop cx
				add si, 2
				loop loop_zone

				pop es
				pop si
				pop ds
				pop dx
				pop bx
				pop cx
				pop ax
				ret

	divdw:		push ds
				push dx
				push cx
				push ax 

				mov ax, div
				mov ds, ax
				mov dx, 0
				;由于本程序中被除数是16位,但是divdw是32/16,所以我们需要将被除数的高位补16个0,也就是将dx置0
				mov ax, dx
				mov dx, 0
				div cx
				;ax存放商,dx存放余数
				;根据公式,使用被除数高位除以除数得到的商×65536
				;*65536等价于在低位加16个0,因此操作就会变得非常简单
				;使用被除数高位除以除数得到的余数×65536+被除数的低位,再将得到的结果除以除数
				;两者的结果相加,即可得到32位/16位的无溢出结果
				push dx
				;使用栈临时保存余数
				mov dx, ax
				mov ax, 0
				mov ds:[0], ax
				mov ds:[2], dx
				pop dx
				;弹出余数,作为右操作数中被除数的高16位
				pop bx
				;得到被除数的低16位
				push bx
				;恢复栈顶数据,避免对主程序造成干扰
				;add ax, bx
				;将右操作数[]中的左操作数的低16位和被除数的低16位相加
				;但是右操作数[]中的左操作数的低16位一定是全0的,因此我们可以省略这一步
				;直接执行mov ax, bx
				mov ax, bx
				div cx
				;ax存放商,dx存放余数
				;由于左操作数的低16位一定是全0,所以不必与其相加,直接将
				;右操作数的低16位存储到ds:[0]内存单元即可
				mov ds:[0], ax
				;商的低16位放到ds:[0]单元中
				mov ds:[4], dx  
				;余数放到ds:[4]单元中
				;ds:[2]中一直保存的都是商的高16位,且没有被更改过,因此无须任何操作

				pop ax
				pop cx
				pop dx
				mov ax, ds:[0]
				;ax保存商的低16位
				mov dx, ds:[2]
				;dx保存商的高16位
				mov cx, ds:[4]
				;cx保存余数  
				pop ds
				;之所以要在pop ds之前将数据转移,是因为子程序divdw调用前,ds已经被使用
				;指向的是其他的段,如果不在pop之前转移数据,那么div段的数据就无法获取了
				ret

	show_str:	push bx
				push cx
				push dx
				push ds
				push es
				push di
				push ax
				push si
				;根据上节中的框架,为了不让子程序干扰主程序中寄存器的值,将所有子程序会用到的寄存器进行压栈

				mov di, ax
				mov ax, 0b800h
				mov es, ax
				;颜色区的段地址
				mov ax, ascii
				mov ds, ax
				;待输出的ASCII码值数据段
				mov al, 160 
				mul dh
				;每行占160个字节,乘以行数
				push ax
				;将行计算的结果存储到栈中
				mov al, 2
				mul dl
				;每列占2个字节,乘以列数
				pop bx
				;将上次运算的结果(160×行数)的值转移到bx中
				add bx, ax	;此时的ax值为(2×列数)
				;将两者相加,最终结果保存到bx中
				mov dl, cl
				;因为下面的跳转指令jcxz需要用到cx寄存器,故需要将cl的值先保存在dl中

	change:		mov cl, ds:[di]
				mov ch, 0 
				inc di
				;我们需要记录下di的值,下一轮循环还会用到它
				;这样一来,我们就需要调整入栈和出栈寄存器的位置了
				;我们在pop di之前pop ax,然后使用ax来保存di的值
				jcxz ok2
				mov ch, dl 
				mov es:[bx+si], cx
				add si, 2
				jmp short change

	ok2:		pop si
				pop ax
				mov ax, di
				pop di
				pop es
				pop ds
				pop dx
				pop cx
				pop bx 
				ret
	code ends
	end start 

运行结果:
王爽《汇编语言》第三版 第十章 实验十_第2张图片

你可能感兴趣的:(汇编,操作系统)