「汇编语言 第 3 版 王爽」- 参考答案:课程设计 1

  CREATED BY JENKINSBOT

第一步、调整 dtoc 程序

在实验 10 中编写 dtoc 程序能够显示数值,但是存在以下问题:
1)原有 dtoc 只能支持 16 位被除数,但是收入数据是 32 位,需要添加支持;
2)除法存在溢出问题,并且原有 dtoc 程序直接使用 DIV 指令,因此可能溢出;

首先调整 dtoc 程序,使其能处理除法溢出问题,这只需使用我们之前编写的 divdw 函数即可。为了清晰问题,我们去掉原有注释:

; 将数值转化为字符换(使用函数 divdw 处理溢出)
; @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, dx		; 调用 divdw 函数,高位 dx
	mov ax, ax		; 调用 divdw 函数,地位 ax
	mov cx, 10		; 调用 divdw 函数,除数 cx
	call divdw		; 调用 divdw 函数
	add cx, 30H		; 余数加 30H 得到对应的字符
	push cx			; 计算结果与显示结果是相反的,所以先入栈,以后再弹出
	mov cx, 0		; 开始判断商是否为零,以决定是否进入下轮
	or cx, ax
	or cx, dx
	jcxz _do_some_stuff_end ; 如果 cx 为零,则表示 ax dx 都为 零,即商为零
	jmp short _do_some_stuff_start
	_do_some_stuff_end: ; 至此,数字对应的字符串都在栈中。“栈底”为零,作为 pop 边界
	;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
	; 计算数值字符串(重点调整的部分 结束)
	;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

	mov bx, 0
	save_to_dateseg:
	pop cx
	jcxz _append_zero_to_sting
	mov ds:[bx], cl
	inc bx
	jmp short save_to_dateseg

	_append_zero_to_sting:
	mov byte ptr ds:[bx], 0

	dtoc_end:
	pop dx
	pop cx
	pop bx
	pop ax
	ret

第二步、某些注意事项

在这个课程设计之前,我们已经编写 show_str divdw dtoc 程序,我们会直接利用这些程序,而不会再编写新程序。

这就带来某些限制,比如说:

1)我们想将 单行数据写入内存 之后,然后再调用 show_str 函数,这样总共调用 21 次。但是 dtoc 程序生成的字符串是包含零字符的,因此没有办法生成单个字符串。因此我们只能写一个列之后,就立刻调用 show_str 函数。

第三步、编写 append_space 程序

根据题目要求,每个字段(列)的宽度是固定的。因此我们需要某种函数,该函数可以追加空格来调整字符串的宽度:

; 将内存中的字符串扩展到指定长度
; @desc ds:si => 字符串地址,bx => 字符串 最终宽度
append_space:
	push cx
	push si
	push bx

	_find_zero_start:
	mov ch, 0
	mov cl, ds:[si]
	jcxz _find_zero_start_end
	inc si
	sub bx, 1
	jmp short _find_zero_start
	_find_zero_start_end:

	mov cx, bx
	mov bl, 20H
	append_space_start:
	mov ds:[si], bl
	inc si
	loop append_space_start

	; 为字符串结尾
	mov bl, 0
	mov ds:[si], bl

	pop bx
	pop si
	pop cx
	ret

第四步、最终的程序

由于篇幅原因,我们去除 show_str divdw 等函数的注释,只保留主体的注释:

assume cs:codeseg

datasg segment
    db '1975','1976','1977','1978','1979','1980','1981','1982','1983'
    db '1984','1985','1986','1987','1988','1989','1990','1991','1992'
    db '1993','1994','1995'
    dd 16,22,382,1356,2390,8000,16000,24486,50065,97479,140417,197514
    dd 345980,590827,803530,1183000,1843000,2759000,3753000,4649000,5937000
    dw 3,7,9,13,28,38,130,220H,476,778,1001,1442,2258,2793,4037,5635,8226
    dw 11542,14430,15257,17800
datasg ends

table segment
    db 160 dup(0)
table ends

codeseg segment

start:
		mov dh, 1 ; 设置显示行号

		; 21 条数据,21 次循环
		mov cx, 21
		; 用做下标,获取数据
		mov bp, 0
		mov di, 0
		; 数据写入 table 段,ds 代表号
		mov ax, table
		mov ds, ax
		mov si, 0
		; 每次循环都要取年份、收入、人数数据
		mov ax, datasg
		mov es, ax ; 年份 => es:[0],收入 => es:[84],人数 => es:[168]

loop_01:
		push cx
		mov cx, 2 ; 设置显示颜色
		; mov dh, dh ; 设置显示行号

		; 写入年份 => 显存
		mov ax, es:0[bp] 		; 开始/初始化字符串
		mov ds:[0], ax
		mov ax, es:0[bp+2]
		mov ds:[2], ax
		mov al, 0
		mov ds:[4], al
		mov bx, 20 				; 开始/调整字符串宽度 => 20
		call append_space
		mov dl, 1				; 开始/显示字符串
		call show_str

		; 写入收入
		push dx					; 开始/初始化字符串
		mov ax, es:84[bp]
		mov dx, es:84[bp+2]
		call dtoc
		pop dx
		mov bx, 20				; 开始/调整字符串宽度 => 20
		call append_space
		mov dl, 21				; 开始/显示字符串
		call show_str

		; 写入人数
		push dx					; 开始/初始化字符串
		mov dx, 0
		mov ax, es:168[di]
		call dtoc
		pop dx
		mov bx, 20				; 开始/调整字符串宽度 => 20
		call append_space
		mov dl, 41				; 开始/显示字符串
		call show_str

		; 写入平均收入
		push dx					; 开始/初始化字符串
		push cx
		mov dx, es:84[bp+2]
		mov ax, es:84[bp]
		mov cx, es:168[di]
		call divdw ; dx, ax...cx
		call dtoc
		pop cx
		pop dx
		mov bx, 20				; 开始/调整字符串宽度 => 20
		call append_space
		mov dl, 61				; 开始/显示字符串
		call show_str

		; 调整变量,进入下一轮循环
		add bp, 4				; 取值的索引需要调整
		add di, 2
		inc dh					; 行号需要调整

		; 进入下一轮循环
		pop cx
		loop loop_01

		mov ax, 4c00h
		int 21h

; 将内存中的字符串扩展到指定长度
; @desc ds:si => 字符串地址,bx => 字符串 最终宽度
append_space:
	push cx
	push si
	push bx

	_find_zero_start:
	mov ch, 0
	mov cl, ds:[si]
	jcxz _find_zero_start_end
	inc si
	sub bx, 1
	jmp short _find_zero_start
	_find_zero_start_end:

	mov cx, bx
	mov bl, 20H
	append_space_start:
	mov ds:[si], bl
	inc si
	loop append_space_start

	; 为字符串结尾
	mov bl, 0
	mov ds:[si], bl

	pop bx
	pop si
	pop cx
	ret

; 将数值转化为字符换(使用函数 divdw 处理溢出)
; @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, dx
	mov ax, ax
	mov cx, 10
	call divdw
	add cx, 30H
	push cx
	mov cx, 0
	or cx, ax
	or cx, dx
	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
	_append_zero_to_sting:
	mov byte ptr ds:[bx], 0
	dtoc_end:
	pop dx
	pop cx
	pop bx
	pop ax
	ret

; 显示字符串
; @desc 参数:dh => 行,dl => 列,cl => 颜色,ds:si => 字符串地址,以 0 结尾
show_str:
	_show_str_start:
	push ax
	push bx
	push dx
	push cx
	push es
	push si
	mov ax, 0B800H
	mov es, ax
	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
	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

; 解决除法溢出问题
; @desc 参数:dx => 被除数高位,ax => 被除数低位,cx => 除数
; @desc 结果:dx => 结果高位,ax => 结果低位,cx => 余数
divdw:
	push bx
	push ax
	mov ax, dx
	mov dx, 0
	div cx
	mov bx, ax
	pop ax
	div cx
	mov cx, dx
	mov dx, bx
	pop bx
	ret

codeseg ends

end start

最终测试结果

参考文献

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