实验十 编写子程序《汇编语言》- 王爽

一. 显示字符串

1. 需求

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

        子程序描述

        名称:show_str

        功能:在指定的位置,用指定的颜色,显示一个用0结尾的字符串

        参数:(dh) = 行号(取值范围在0 ~ 24),dl = 列号(取值范围在0 ~ 79),

                   (cl) = 颜色,ds:si指向字符串的首地址

        返回值:无

2. 分析

        在8086CPU中,B8000h ~ BFFFFh 这32kb空间,是80 x 25彩色字符模式的显示缓冲区,显示缓冲区分为8页,每页有4kb(约等于4000字节),显示器可以显示任意一页的内容,一般情况下,显示第0页的内容,也就是说,B8000h ~ B8F9F 这4000个字节的内容会显示在显示器上。所以,可以让es寄存器指向显示缓冲器的首地址,作为显示缓冲区的段地址:es = 0B800h

        在 B8000h ~ B8F9F 这4000个字节中,分为25行,每一行占160个字节,显示80个字符,每个字符占2字节,低字节存放字符的assii码,高字节存放字符的属性,关于字符属性(闪烁,背景(底色),高亮,前景(字符色))等介绍,可以浏览以下博文了解实验9 根据材料编程《汇编语言》- 王爽-CSDN博客

所以,绿色字的字符属性为:0000 0010b = 2

3. 代码

示例:在屏幕的 8 行 3 列,用绿色显示data段中的字符串

assume cs:code, ds:data
data segment
    db 'welcome to masm!',0
data ends
code segment
start:
    mov ax, data
    mov ds, ax

    mov dh, 8
    mov dl, 3
    mov cl, 2   ;字属性字节
    mov si, 0
    call show_str

    mov ax, 4c00h
    int 21h

;名称:show_str
;功能:在指定的位置,用指定的颜色,显示一个以0结尾的字符串
;参数:dh=行号(取值范围:0-24),dl=列号(取值范围:0-79)
;      cl=颜色,ds:si指向字符串的首地址
;返回值:无
show_str:
    ;B8000h-BFFFFh共32kb,是80 * 25彩色字符模式的显示缓冲区,每页4kb(4000字节),显示器可以显示任意一页的内容。
    ;一般情况下,显示第0页的内容, 也就是说,B8000h ~ B8F9Fh 这4000个字节的内容会显示在显示器上。每行80个字,
    ;每个字占2个字节,低字节存放字符的assii码,高字节为属性字节。所以每行占160个字节
    mov ax, 0B800h  ;显示缓冲区段地址
    mov es, ax
    
    ;行
    mov al, 160     ;每行160个字节
    mul dh          ;8位乘法,一个数默认在al中,另一个在dh中,结果放在ax中
    mov bx, ax      ;行偏移字节
    ;列
    mov al, 2       ;一个字占2个字节,低字节存放字符的assii码,高字节为属性字节
    mul dl          ;8位,乘法,一个数默认在al中,另一个在dl中,结果放在ax中
    add bx, ax      ;行偏移字节 + 列偏移字节 = 相对于显示缓冲区段地址总的偏移地址

    mov ch, 0
    mov dl, cl      ;循环中需要用到cx寄存器,所以把存放字体属性的字节存放在dl中
s:
    mov cl, ds:[si]
    jcxz ok         ;cx = 0,跳转到标号处执行,否则,继续顺序往下执行
    mov es:[bx], cl
    mov es:[bx+1], dl
    inc si
    add bx, 2
    loop s
     
ok:    
    ret

code ends
end start

二. 解决除法溢出问题

1. 需求

        div 指令做除法,当进行8位除法的时候,用al存储结果的商,ah存储结果的余数;当进行16位除法的时候,用ax存储结果的商,dx存储结果的余数。可是现在有一个问题,如果结果的商大于al或ax所能存储的最大值,那么将引发CPU的一个内部错误,这个错误被称为:除法溢出,我们可以用下面的子程序 divdw 解决

        子程序描述

        名称:divdw

        功能:进行不会产生溢出的除法运算,被除数为 dword 型,除数为 word 型,结果为 dword 型

        参数:(ax) = dword 型数据的低16位

                   (dx) = dword 型数据的高16位

                   (cx) = 除数

        返回:(ax) = 结果的低16位,(dx) = 结果的高16位

                   (cx) = 结果的余数

2. 分析

数学公式:X/N = int(H/N)*65535 + [rem(H/N)*65535 + L]/N

X:被除数,范围:[0, FFFFFFFF]

N:除数,范围:[0, FFFF]

H:X的高16位,范围:[0, FFFF]

L:X的低16位,范围:[0, FFFF]

int():描述性运算符,取商,比如,int(38/10) = 3

rem():描述性运算符,取余,比如,int(38/10) = 8

所以,dword 型数据 X 除以 word 型数据 N,可以转换成等号右边的公式来表达,而等号右边的所有除法运算都可以用div指令来做,而且肯定不会导致溢出问题。

3. 代码

示例:计算1000000/10(F4240h/0Ah)

assume cs:code, ds:stack
stack segment   
    dw 16 dup (0)
stack ends
code segment
start:
    ;初始化数据段
    mov ax, stack
    mov ss, ax
    mov sp, 16

    mov ax, 4240h   ;ax = 被除数的低16位
    mov dx, 0Fh     ;dx = 被除数的高16位
    mov cx, 0Ah     ;cx = 除数

    call divdw

    mov ax, 4c00h
    int 21h

    ;1000000/10(F4240h/0Ah) = int(H/N)*65535 + [rem(H/N)*65535 + L]/N
    
    ;名称: divdw
    ;功能: 进行不会产生溢出的除法运算,被除数为 dword 型,除数为 word 型,结果为 dword 型
    ;参数: (ax) = dword 型数据的低16位
    ;      (dx) = dword 型数据的高16位
    ;      (cx) = 除数
    ;返回: (ax) = 结果的低16位,(dx) = 结果的高16位
    ;      (cx) = 结果的余数
divdw:
    push si         ;保存原si
    push ax         ;被除数的低16位
    push dx         ;被除数的高16位

    ;int(H/N)*65535
    pop ax          ;被除数的高16位
    mov dx, 0
    div cx          ;16位除法,被除数为32位,ax存放被除数的低16位,dx存放被除数的高16位
                    ;结果的商存在ax,结果的余存在dx
    mov si, ax      ;si暂存最后结果的高16位,最后要还原到dx寄存器中的

    ;[rem(H/N)*65535 + L]/N     ;上一个除法,已经将rem(H/N)*65535放在dx寄存器中了
    pop ax          ;被除数的低16位, 也就是公式中的 L
    div cx          ;16位除法,被除数为32位,ax存放被除数的低16位,dx存放被除数的高16位
                    ;结果的商存在ax,结果的余存在dx
    mov cx, dx      ;余数入cx
    mov dx, si      ;高16位入dx
    pop si          ;还原原si

    ret

code ends
end start

我们知道,1000000/10 的商为 100000 = 186A0h,余数为 0,程序执行的结果为:ax = 86A0h,dx = 1,cx = 0,所以我们的程序代码是正确的。

三. 数值显示

1. 需求

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

data segment

        dw 123, 12666, 1, 8, 3, 38

data ends

2. 分析

(1)根据书中材料,我们可以知道,字符'0' ~ '9'的assii码为30h ~ 39h,所以对于数字0 ~ 9,他们对应的字符的assii码为:数字的值 + 30h

(2)对于一个数字,比如:12666,对这个数字循环取余,直到余数为0时,就可以得到每这个数字所有的数码:1,2,6,6,6,对每个数码 + 30h得到每个数码对应的字符的assii码从而得到对应的字符串。然后通过需求一:显示字符串的show_str子程序显示在显示器上。

3.代码

assume cs:code, ds:data, ss:stack
stack segment
    db 32 dup (0)
stack ends
data segment
    db 10 dup (0)
data ends

code segment
start:
    mov ax, stack
    mov ss, ax
    mov sp, 32

    mov ax, data
    mov ds, ax

    mov ax, 12666
    mov si, 0
    call dtoc

    mov dh, 8
    mov dl, 3
    mov cl, 2
    mov si, 0
    call show_str

    mov ax, 4c00h
    int 21h

;名称:dtoc
;功能:将dword型数据转为表示十进制数的字符串,字符串以 0 为结尾
;参数:(ax) = dword 型数据
;     (ds:si)指向字符串的首地址
;返回:无
dtoc:      
    mov bx, 0         ;bx用来记录数字的总数码数,比如12666有5个
    mov di, 10        ;十进制数
loop_dtoc:
    mov dx, 0         ;16位除法,被除数的低16位在ax,被除数的高16位为0
    div di            ;16位除法,结果的商在ax中,余数在dx中
    mov cx, dx        ;余数入cx
    jcxz ok_dtoc      ;cx = 0,则跳转到标号处执行,否则继续顺序往下执行
    add dx, 30h       ;数字+30h = 数字对应的字符的assii值
    push dx           ;字符入栈
    inc bx
    loop loop_dtoc
ok_dtoc:
    mov cx, bx
char_nums:
    pop ax
    mov ds:[si], al         ;因为assii码的值不会大于255的,所以用 al
    inc si                  ;ds:si指向下一个字符单元
    loop char_nums
    mov byte ptr ds:[si], 0 ;字符串以0结尾  
    ret 

;名称:show_str
;功能:在指定的位置,用指定的颜色,显示一个以0结尾的字符串
;参数:dh=行号(取值范围:0-24),dl=列号(取值范围:0-79)
;      cl=颜色,ds:si指向字符串的首地址
;返回值:无
show_str:
    ;B8000h-BFFFFh共32kb,是80 * 25彩色字符模式的显示缓冲区,每页4kb(4000字节),显示器可以显示任意一页的内容。
    ;一般情况下,显示第0页的内容, 也就是说,B8000h ~ B8F9Fh 这4000个字节的内容会显示在显示器上。每行80个字,
    ;每个字占2个字节,低字节存放字符的assii码,高字节为属性字节。所以每行占160个字节
    mov ax, 0B800h  ;显示缓冲区段地址
    mov es, ax
    
    ;行
    mov al, 160     ;每行160个字节
    mul dh          ;8位乘法,一个数默认在al中,另一个在dh中,结果放在ax中
    mov bx, ax      ;行偏移字节
    ;列
    mov al, 2       ;一个字占2个字节,低字节存放字符的assii码,高字节为属性字节
    mul dl          ;8位,乘法,一个数默认在al中,另一个在dl中,结果放在ax中
    add bx, ax      ;行偏移字节 + 列偏移字节 = 相对于显示缓冲区段地址总的偏移地址

    mov ch, 0
    mov dl, cl      ;循环中需要用到cx寄存器,所以把存放字体属性的字节存放在dl中
loop_show_str:
    mov cl, ds:[si]
    jcxz ok_show_str        ;cx = 0,跳转到标号处执行,否则,继续顺序往下执行
    mov es:[bx], cl
    mov es:[bx+1], dl
    inc si
    add bx, 2
    loop loop_show_str
     
ok_show_str:    
    ret
code ends
end start

  • 24
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值