王爽 汇编语言个人疑问汇总第七篇

第十章

实验 10 编写子程序

1. 显示字符串

原程序
assume cs:code

data segment
	db 'Welcome to masm!',0
data ends

code segment
start:
	mov dh,8     ; 第8行
	mov dl,3     ; 第3列
	mov cl,2     ; 颜色 2 的二进制是10,
				 ; 参考书上实验9 根据材料编程对颜色的说明可得知
				 ; 此处表示绿色
					
	mov ax,data  ; 代码段data指向ax寄存器
	mov ds,ax    ; (ds) = (ax)
	mov si,0     ; 字符串的首地址
	call show_str  ; 调用子程序
	
	mov ax,4c00h  ; 固定的结束代码
	int 21h       ; 固定的结束代码
	
show_str:	.
            .
			
			.
			.
			.
			. 
code ends
end start
最终代码
assume cs:code

data segment
	db 'Welcome to masm!',0
data ends

code segment
start:
	mov dh,8     ; 第8行
	mov dl,3     ; 第3列
	mov cl,2     ; 颜色 2 的二进制是10,
				 ; 参考书上实验9 根据材料编程对颜色的说明可得知
				 ; 此处表示绿色
					
	mov ax,data  ; 代码段data指向ax寄存器
	mov ds,ax    ; (ds) = (ax)
	mov si,0     ; 字符串的首地址
	call show_str  ; 调用子程序
	
	mov ax,4c00h  ; 固定的结束代码
	int 21h       ; 固定的结束代码
	
show_str:	;其实这是一个子程序,里面是包含s和ok的,这样理解容易些
	push ax
	push bx
	push dx
	push cx
	push es
	push si       ; 保存程序中用到的相关寄存器,对于这个程序中其实并没有使用到从栈中取出的寄存器数据,
				  ; 个人觉得可能是一种编码习惯吧,放到栈中保护现场,以便后续的功能用到


	mov al,160  ; 每行80个字符,所以每行占160个字节(参考参考书上实验9 根据材料编程对颜色的说明可得知), 160 和 下面dh的值都小于255,做8位乘法
	mul dh      ; (dh) = 8
	mov bx,ax   ; 160 * dh 运算结果在ax中 , 再赋值给bx ,计算的行的偏移量
	mov al,2    ; 计算列的偏移量,每列占2个字节
	mul dl       ; (dl) = 2
	add bx,ax	 ;计算出行和列加起来的偏移量,放在bx里
	sub bx,2     ; 列号从0开始,最终结果要-2 才会显示在第3列
	mov ax,0B800h  ; 显示缓冲区固定地址
	mov es,ax	  ;通过段寄存器es来操控要显示的内容,es:[bx]为要输入的显示区
	mov al,cl	 ;先将cl中的颜色放在al
	mov ch,0	 ;cx高位ch为0,因为用不上
	
s:	 
	mov cl,[si]        ; 把代码段中的数据赋值给cx寄存器低位cl
	jcxz ok            ;  利用jcxz指令特性,cx=0时就会调用ok代码段,程序调用ok代码段,最后返回。
	mov es:[bx],cl     ; 低位存字符
	mov es:[bx+1],al   ; 高位存颜色
	add bx,2           ; 往前偏移2个字节(1个字节存字符,1个字节存颜色),准备存下一个字符
	inc si             ; 下一个字符的偏移量
	jmp short s        ; 跳转到s代码段第一行指令
	
ok:	;返回
	pop si   ; 把前面放入栈中的寄存器,依次取出来,相当于还原了
	pop es
	pop cx
	pop dx
	pop bx
	pop ax    
	
	ret

code ends
end start
  • 运行结果
    在这里插入图片描述
  • 我们再来修改下显示的行和列,让它显示在第10行5列,就能体会到子程序的妙用了。
    在这里插入图片描述
    在这里插入图片描述
  • 可以看出,我们简单修改下参数就能控制字符串的显示了,相当于show_str就是一个封装起来的函数,调用方不用管具体逻辑,传参就可以了。
程序分析
  • mov cl,2,这是颜色 2 的二进制是10,参考书上实验9 根据材料编程对颜色的说明可得知,此处表示绿色。
  • show_str中有保存基础的操作,对于这个程序中其实并没有使用到从栈中取出的寄存器数据,个人觉得可能是一种编码习惯吧,放到栈中保护现场,以便后续的功能用到。
  • 每行160个字节,; 每行80个字符,所以每行占160个字节(参考参考书上实验9 根据材料编程对颜色的说明可得知)。
  • 显示字符串的偏移量bx,列号从0开始,还要减2个字节,不然是在第4列开始。
  • 利用jcxz指令特性,cx=0时就会调用ok代码段,程序调用ok代码段,最后返回。
本题参考

2. 解决除法溢出的问题

程序分析
  • 按照小甲鱼视频中对 X/N = int(H/N)*65536+[rem(H/N)*65536+L]/N公式的分析。他以328/2为例:
    在这里插入图片描述
  • 把328分成2段,高位是3,低位是28
  • 3/2,等于1,余下1
  • 然后128/2 等于64
  • 最后商的高位是1,低位是64,余数是0。
  • 下面用代码实现这个算法。
最终代码
assume cs:code
code segment
  start:mov ax,4240h
		mov dx,000fh
		mov cx,0ah
		call divdw
		
        mov ax,4c00h
        int 21h
		
divdw:	push ax		; 压入L,先对H进行运算
		mov ax,dx   ; 被除数高位dx寄存器的值赋值给ax寄存器
		mov dx,0	; 清空dx寄存器,不影响余数位,使得高16为0,  
		div cx      ; 执行后, ax存储结果的商,dx存储结果的余数
		
		pop bx		; 先取出L,赋值给bx寄存器
		push ax     ; 再把运算出H/N 的商,压入栈中
		mov ax,bx   ; 此时ax为L
		div cx      ; L / N  ,注意:16位除法的时候默认被除数dx为高16位,ax为低16位 ,
					; 相当于 [rem(H/N)*65536+L]/N
					;执行后, ax存储结果的商,dx存储结果的余数 
		mov cx,dx   ; 把 L / N 的 余数赋值给 cx寄存器 
		pop dx		; 取出高位/除数的商,赋值给dx
		
		ret         ; 返回
code ends
end start


本题参考

https://blog.csdn.net/weixin_44223946/article/details/108906074
https://www.bilibili.com/video/BV1Rs411c7HG?p=52

3. 数值显示

程序分析
  • 显示字符串的功能,我们几乎完全可以复用前面第2题的程序。
  • 要注意,显卡遵循ASCII编码,我们要得到每一位对应 ASCII码值,ASCII码值 = 十进制数码值 + 30H。先求每位的10进制数,也就是实现下图的功能。
    在这里插入图片描述
  • 得到每一位的10进制数 + 30H
完整代码
  • 下面dtoc子程序。 就是实现得到每位ASCII码,并存放在data数据段中。
 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    ; ds段寄存器指向data数据段
	mov si,0
	
	call dtoc    ; 转为ASCII码的子程序,并且存在数据段中,操作ds段寄存器就可以放进去了
	
	mov dh,8     ; 第8行
	mov dl,3     ; 第3列
	mov cl,2     ; 颜色 2 的二进制是10,
				 ; 参考书上实验9 根据材料编程对颜色的说明可得知
				 ; 此处表示绿色
					
   ;mov ax,data  ; 代码段data指向ax寄存器  ,这2段代码在此程序中可以不要了
   ;mov ds,ax    ; (ds) = (ax)
   
	mov si,0     ; 字符串的首地址
	call show_str  ; 调用子程序
	
	mov ax,4c00h  ; 固定的结束代码
	int 21h       ; 固定的结束代码
	
dtoc:
	push dx
	push cx
	push ax
	push si
	push bx     ; 记录之前寄存器的数据
	
	mov bx,0 	; bx用于记录有多少个数,作为s3代码段的循环次数,真正数据暂存在栈中
	
	
s1:
	mov cx,10   ; 除数
	mov dx,0    ; 先把余数置为0,避免影响计算
	
	
	div cx      ; 除以10
	mov cx,ax   ; 商赋值给cx寄存器 
	 
	jcxz s2     ; 如果前面商为0,即cx=0,那么就会跳到s2代码段第一行指令
	
	add dx,30H  ; 余数 + 30h, ASCII码值 = 十进制数码值 + 30H
	push dx     ; ASCII码值暂存到栈中
	
	inc bx      ; 记录有多少个数
	
	jmp short s1  ; 跳转到s1代码段第1行指令
	
s2:
	add dx,30H  ; 最后的余数 + 30H
	push dx     ; 放入栈中
	inc bx      ; 计数+1
	
	mov cx,bx   ; 计算赋值给cx寄存器,下面的loop使用,循环次数
	mov si,0    ; 从0开始,ds段寄存器的位置
	
s3: 
	pop ax      ; 取出栈中数据给ax寄存器
	 
	mov [si],al  ; 意为 ds:[si] ,al
	inc si       ; 累加1,数据段下一个字节
	
	loop s3       ; 循环
	
okay:
	pop bx
	pop si
	pop ax
	pop cx
	pop dx   ; 把之前数据依次取出
	
	ret     ; 返回

	
show_str:	;其实这是一个子程序,里面是包含s和ok的,这样理解容易些
	push ax
	push bx
	push dx
	push cx
	push es
	push si       ; 保存程序中用到的相关寄存器,对于这个程序中其实并没有使用到从栈中取出的寄存器数据,
				  ; 个人觉得可能是一种编码习惯吧,放到栈中保护现场,以便后续的功能用到


	mov al,160  ; 每行80个字符,所以每行占160个字节(参考参考书上实验9 根据材料编程对颜色的说明可得知), 160 和 下面dh的值都小于255,做8位乘法
	mul dh      ; (dh) = 8
	mov bx,ax   ; 160 * dh 运算结果在ax中 , 再赋值给bx ,计算的行的偏移量
	mov al,2    ; 计算列的偏移量,每列占2个字节
	mul dl       ; (dl) = 2
	add bx,ax	 ;计算出行和列加起来的偏移量,放在bx里
	sub bx,2     ; 列号从0开始,最终结果要-2 才会显示在第3列
	mov ax,0B800h  ; 显示缓冲区固定地址
	mov es,ax	  ;通过段寄存器es来操控要显示的内容,es:[bx]为要输入的显示区
	mov al,cl	 ;先将cl中的颜色放在al
	mov ch,0	 ;cx高位ch为0,因为用不上
	
s:	 
	mov cl,[si]        ; 把代码段中的数据赋值给cx寄存器低位cl
	jcxz ok            ;  利用jcxz指令特性,cx=0时就会调用ok代码段,程序调用ok代码段,最后返回。
	mov es:[bx],cl     ; 低位存字符
	mov es:[bx+1],al   ; 高位存颜色
	add bx,2           ; 往前偏移2个字节(1个字节存字符,1个字节存颜色),准备存下一个字符
	inc si             ; 下一个字符的偏移量
	jmp short s        ; 跳转到s代码段第一行指令
	
ok:	;返回
	pop si   ; 把前面放入栈中的寄存器,依次取出来,相当于还原了
	pop es
	pop cx
	pop dx
	pop bx
	pop ax    
	
	ret

code ends
end start

  • 运行结果
    在这里插入图片描述
本题参考

https://www.bilibili.com/video/BV1Rs411c7HG?p=53

posted on 2021-11-19 14:56  愤怒的苹果ext  阅读(33)  评论(0编辑  收藏  举报

导航