《汇编语言》实验10 编写子程序

1. 显示字符串

显示字符串是现实工作中经常要用到的功能,应该编写一个通用的子程序来实现这个功能。我们应该提供灵活的调用接口,使调用者可以决定显示的位置(行、列)、内容和颜色。

子程序描述

名称:show_str
功能:在指定的位置,用指定的颜色,显示一个用0结束的字符串。
参数:(dh)=行号(取值范围0 ~ 24),(dl)=列号(取值范围0 ~ 79),(cl)=颜色,ds:si指向字符串的首地址。
返回:无

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
; 名称:show_str
; 功能:在指定的位置,用指定的颜色,显示一个用0结束的字符串。
; 参数:(dh)=行号(取值范围0~24), (dl)=列号(取值范围0~79),
;      (cl)=颜色,ds:si 指向字符串的首地址
; 返回:无
show_str:
    push ax
    push dx
    push cx
    push bx

    ; 计算指定行号的偏移地址
    mov al,160
    mov ah,0
    mul dh
    mov bx,ax

    ; 计算列号偏移地址
    mov al,2
    mov ah,0
    mul dl
    
    ; 把列号偏移地址加到行号偏移地址中,保存在bx
    add bx,ax  

    ; 打印字符串
    push es
    push di
    print_str:

        ; 把显存地址B800段地址放入ES段寄存器
        mov ax,0B800H
        mov es,ax
        ; di作为显存地址的字符列偏移地址,每个字符占2字节
        mov di,0

        ; 先把cl中的颜色保存到al中,因为后面会用到cx做为跳转判断字符串是否结束 
        mov al,cl
        print_str_put:
            mov ch,[si] ; 把字符串放入ch,并把cl置为0
            mov cl,0
            jcxz ok ; 判断cx中的字符串是否为0,如果是说明字符串已结束,跳转到OK处
            
            mov byte ptr es:[bx][di],ch
            mov byte ptr es:[bx][di][1], al
            add si,1    ; si作为字符串偏移地址
            add di,2    ; di作为显存偏移地址
        loop print_str_put

    ok:
    pop di
    pop es

    pop bx
    pop cx
    pop dx
    pop ax
    ret
; show_str 

2. 解决除法溢出的问题

问题

前面读过,div指令可以做除法。当进行8位除法的时候,用al存储结果的商,ah存储结果的余数;进行16位除法的时候,用ax存储结果的商,dx存储结果的余数。可是,现在有一个问题,如果结果的商大于al或ax所能存储的最大值,那么将如何?

子程序描述

名称:divdw
功能:进行不会产生溢出的除法去处,被除数为dword型,除数为word型,结果为dword型。
参数:(ax)=dword型数据的低16位
(dx)=dword型数据的高16位
(cx)=除数
返回:(dx)=结果的高16位,(ax)=结果的低16位,(cx)=余数
公式:X/N = int(H/N)*65535 + [rem(H/N)*65535+L]/N

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
; 名称:divdw
; 功能:进行不会溢出的除法运算,被除数为dword型,除数为word型,结果为dword型
; 参数:(ax)=dword型数据的低16位
;       (dx)=dword型数据的高16位
;       (cx)=除数
; 返回:(dx)=结果的高16位,(ax)=结果的低16位
;       (cx)=余数
; 公式:X/N = int(H/N)*65536 + (rem(H/N)*65536+L)/N
divdw:
    push bx
    push ax     ; 把ax中的被除数低位暂存 L

    mov ax,dx   ; 把dx放入低ax位计算 H/N
    mov dx,0
    div cx      ; H/N 商在AX,余数在DX

    mov bx,ax   ; 暂存 H/N 商

    ; mov dx, dx ; 因为 rem(H/N)*65536 = rem(H/N)*10000H 即左移4位,所以上次余数在DX中不用动
    pop ax  ; 取出被除数低位 L
    div cx  ; 商在AX,余数在DX

    ; mov ax,ax ; 低位的商在ax中不变
    mov cx,dx   ; dx中的余数传送到cx中
    mov dx,bx   ; 高位计算的商传送到dx中

    pop bx
    ret
; divdw

3. 数值显示

问题

编程,将data段中的数据以十进制的形式显示出来。

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

子程序描述

名称:dtoc
功能:将word型数据转变为表示十进制数的字符串,字符串以0为结尾符。
参数:(ax)=word型数据 ,ds:si指向字符串的首地址
返回:无

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
; 名称:dtoc
; 功能:将word型数据转变为表示十进制数的字符串,字符串以0为结尾符。
; 参数:(ax)=word型数据 
;       ds:si指向字符串的首地址
; 返回:无
dtoc:
    push dx
    push cx
    push ax
    push bx

    mov bx,0
    mov dx,0
    push dx       ; push进栈一个0值,用于在pop的时候判断是否已经到字符串尾部
    dtoc_div_start:
        mov dx,0
        mov cx,10
        call divdw

        add cx,30H  ; cx是余数,入栈
        push cx

        mov cx,dx   ; 判断商的dx和ax是否都为0,如果是则结束
        jcxz judge_ax
        loop dtoc_div_start
        judge_ax:
        mov cx,ax   ; 把ax放入cx,用于判断商为0时结束循环
        inc cx
        loop dtoc_div_start

    dtoc_stack_to_data:
        pop dx
        mov byte ptr ds:[si][bx],dl
        inc bx

        mov cx,dx   ; 把dx放入cx,用于判断dx为0时结束循环
        inc cx
        loop dtoc_stack_to_data

    pop bx
    pop ax
    pop cx
    pop dx
    ret