「汇编语言 第 3 版 王爽」- 参考答案:实验 10 编写子程序

  CREATED BY JENKINSBOT

1)显示字符串

assume cs:codeseg, ds:data

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

codeseg 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

; 显示字符串
; @desc 参数:dh => 行,dl => 列,cl => 颜色,ds:si => 字符串地址,以要 0 结尾
show_str:
	;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
	; 保存寄存器
	;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
	show_str_start:
	push ax
	push bx
	push dx
	push cx
	push es
	push si

	;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
	; 开始实现功能
	;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
	; 显存 B8000 - BFFFF,我们使用 B800 作为段地址
	; 段空间位 64K,而显存只有 32K,所以不会超过段

	; 第一步、设置段地址
	mov ax, 0B800H
	mov es, ax ; es => 写入字符的段地址

	; 第二步、计算偏移量
	; 每行的长度:0 - 9F | 每列的宽度:两字节,高位颜色,低位字符
	; 写入字符的开始地址 = (行号 - 1) x 0A0H + 列号 x 2
	sub dh, 1
	mov al, dh
	mov bl, 0A0H
	mul bl ; ax
	add dl, dl
	mov dh, 0 ; dx
	add dx, ax ; ax => 写入字符的开始地址
	mov bx, dx

	; 第三步、开始写入
	mov ah, cl ; 颜色
	copy_char:
	mov cl, ds:[si] ; 判断字符是否为零
	mov ch, 0
	jcxz show_str_end
	mov al, cl
	mov es:[bx], ax ; 将字符写入显存
	inc si
	add bx, 2
	jmp short copy_char

	;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
	; 恢复寄存器
	;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
	show_str_end:
	pop si
	pop es
	pop cx
	pop dx
	pop bx
	pop ax
	ret

codeseg ends

end start

2)解决除法溢出问题

assume cs:codeseg

codeseg segment

start:	mov ax, 4240H ; LOW
		mov dx, 000FH ; HIGH
		mov cx, 0AH
		call divdw

		mov ax, 4c00H
		int 21h

; 解决除法溢出问题
; @desc 参数:dx => 被除数高位,ax => 被除数低位,cx => 除数
; @desc 结果:dx => 结果高位,ax => 结果低位,cx => 余数
divdw:
	;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
	; 保存寄存器
	;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
	; 根据题目,并结合我们的实现方式,只有 bx 寄存器是“借用”的,
	; 其他寄存器(ax, dx, cx)会被使用,并且内容会被改变
	; 因此无需保存
	push bx

	;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
	;; 开始计算除法
	;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

	; 第一部分、计算【int(H/N) * 65536】Start
	push ax ; 由于需要使用 ax 寄存器,所以先保存 ax 寄存器(即初始的低位值)
	mov ax, dx ; 开始计算 int(H/N)
	mov dx, 0
	div cx ; 此时:ax => 商,这就是“结果的高 16 位”;dx => 余数,即 rem(H/N) 值
	mov bx, ax ; 后面需要使用 ax 寄存器,因此将 ax 保存到 bx 中

	; 第二部分、计算【[rem(H/N) * 65536 + L]/N】
	; [rem(H/N) * 65536 + L]/N 是 16 位除法,因为 N 为 16 位(既初始的 cx 寄存器)
	; [rem(H/N) * 65536 + L]/N 等价于:
	; 		(1)将“高位(000FH)除法的余数”放在 dx 寄存器中
	;		(2)将低位(4240H)放在 ax 寄存器中
	;		(3)然后除以 cx 寄存器
	; (1)而前一步的除法已经将余数放在 dx 寄存器中,
	; (2)因此设置 ax 寄存器即可,即将低位放入 ax 中
	; (3)然后除以 cx 寄存器
	pop ax ; 由于 ax 的值之前写入到栈中,此时弹出
	div cx ; => ax...dx

	; 按照实验要求,存储计算结果
	mov cx, dx
	mov dx, bx
	; mov ax, ax ; 由于 ax 已经存储低位,因此无需处理

	;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
	; 恢复寄存器
	;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
	pop bx
	ret

codeseg ends

end start

3)数值显示

assume cs:codeseg

dateseg segment
	db 10 dup (0)
dateseg ends

codeseg segment

	start:
	mov ax, 12666
	mov bx, dateseg
	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

; 将数值转化为字符换
; @desc 参数:dx => 数据高位,ax => 数据低位,ds:si => 数据写入地址
; @desc 结果:保存到 ds:si 中,并以 0 结尾
dtoc:
	;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
	; 保存寄存器
	;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
	push ax
	push bx
	push cx
	push dx

	;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
	; 实现逻辑
	;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
	; 计算数值字符串
	mov bx, 0
	push bx ; 无需理解此步骤,往下看就知道了
	_do_some_stuff_start:
	mov dx, 0
	mov ax, ax
	mov bx, 10
	div bx ; dx,ax / bx = ax...dx
	add dx, 30H
	push dx ; 余数,计算结果与显示结果是相反的。先入栈再弹出
	mov cx, ax
	jcxz _do_some_stuff_end ; 如果商为零,则结束
	jmp short _do_some_stuff_start ; 继续运行,进行下一轮除法
	_do_some_stuff_end:

	; 将数值写到数据段
	mov bx, 0
	save_to_dateseg:
	pop cx ; 最开始我们在栈底写入零,用于标识数据弹出结束(再弹就不是我们的数据了)
	jcxz _append_zero_to_sting ; 跳转到结束
	mov ds:[bx], cl ; 这里保留低位即可,高位没有意义,而且字符只占一个字节
	inc bx
	jmp short save_to_dateseg

	; 字符串末尾追加零,因为 show_str 要求字符串以 0 结尾
	_append_zero_to_sting:
	mov byte ptr ds:[bx], 0

	;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
	; 恢复寄存器
	;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
	dtoc_end:
	pop dx
	pop cx
	pop bx
	pop ax
	ret

show_str:
	;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
	; 保存寄存器
	;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
	_show_str_start:
	push ax
	push bx
	push dx
	push cx
	push es

	;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
	; 开始实现功能
	;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
	; 显存 B8000 - BFFFF,我们使用 B800 作为段地址
	; 段空间位 64K,而显存只有 32K,所以不会超过段

	; 第一步、设置段地址
	mov ax, 0B800H
	mov es, ax ; es => 写入字符的段地址

	; 第二步、计算偏移量
	; 每行的长度:0 - 9F | 每列的宽度:两字节,高位颜色,低位字符
	; 写入字符的开始地址 = (行号 - 1) x 0A0H + (列号-1) x 2
	sub dh, 1
	mov al, dh
	mov bl, 0A0H
	mul bl ; ax
	sub dl, 1
	add dl, dl
	mov dh, 0 ; dx
	add dx, ax ; ax => 写入字符的开始地址
	mov bx, dx

	; 第三步、开始写入
	mov ah, cl ; 颜色
	_copy_char:
	mov cl, ds:[si] ; 判断字符是否为零
	mov ch, 0
	jcxz _show_str_end
	mov al, cl
	mov es:[bx], ax ; 将字符写入显存
	inc si
	add bx, 2
	jmp short _copy_char

	;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
	; 恢复寄存器
	;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
	_show_str_end:
	pop es
	pop cx
	pop dx
	pop bx
	pop ax
	ret

codeseg ends

end start

参考文献

CSDN/汇编语言王爽第三版答案
百度文库/汇编语言实验答案 (王爽)