汇编语言实验十

一、编写一个通用的子程序来实现显示字符串的功能

名称:show_str

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

参数:(dh)= 行号;(dl)= 列号;(cl)= 颜色,ds:si 指向字符串的首地址

返回:无

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

assume cs:code
data segment
   db 'Welcome to masm!',0
data ends

code 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

show_str: 

        ............

code ends
end start

 

本题由于参数的个数不是很多,所以不用将参数放进栈中存储。

在编写子程序之前还是先回答两个问题:

  • 要处理的数据在什么地方?
  • 要处理的数据有多长?

首先本题要处理的数据就是 data 段中的内容,在进行子程序调用之前就已经将 data 段的首地址存储进了寄存器中以备用。

然后就是 data 段的数据有多长的问题,很显然 data 段中的数据是以 0 字符作为结尾,可以使用 jcxz 指令来检测 0 而知道数据是否处理完,所以子程序不需要字符串的长度参数。

下面是子程序的代码:

assume cs:code,ds:data

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

code segment
start:
    mov ax,data
    mov ds,ax
    mov si,0

    mov dh,8
    mov dl,3
    mov cl,2
    call show_str
    mov ax,4c00h
    int 21h


show_str:                  ;这里是显示字符串子程序的入口
    push ax                ;因为子程序会用到相关的寄存器
    push dx                ;与 pop 指令结合进行相关寄存器的保护工作            
    push cx
    push es
    push di
    push si
    
    mov ax,0b800h          ;B800H 是显示缓冲区的首地址的段地址, 
    mov es,ax            
    
    sub ax,ax              ;将 ax 寄存器中的值置零
    mov al,160             ;显示器上的一行总共有 80 个字符, 在显示缓冲区中占有 160 个字节
    mul dh                 ;上面一行字符所占的字节数存放在 al 寄存器中, 作为一个乘数, dh 寄存器中的数据作为另外一个乘数, 结果存放在 ax 中
    sub dh,dh
    add dl,dl              ;由于一个字符占 2 个字节, 所以需要将 dl 中的数据乘以 2
    add ax,dx            
    mov di,ax              ;最后将字符串在显示缓冲区中首字符的地址存放在 di 寄存器中

    mov al,cl         
    sub cx,cx              ;将 cx 寄存器置零, 以备下面 jcxz 使用
  next:
    mov cl,[si]            ;在调用子程序之前就将要操作字符串的首偏移地址存放在了 si 中, 
    jcxz sret              ;判断 cx 中即 ds:[si] 所指的内存单元是否为 0 , 如果为 0, 则跳转到 sret 标号的位置
    mov es:[di],cl
    mov es:[di+1],al       ;在目的地址分别存放字符本身和字符的颜色属性
    inc si                
    add di,2
    jmp short next
   sret:    
    pop si                 ;将寄存器中的值还原, pop 指令的顺序与 push 指令相反
    pop di
    pop es
    pop cx
    pop dx
    pop ax    

    ret                     ;子程序返回 , 继续执行 mov ax,4c00h
code ends
end start

 

二、解决除法溢出的问题

名称:divdw

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

参数:(ax)= dword 型数据的低 16 位;(dx)= dword 型数据的低 16 位;(cx)= 除数

返回:(dx)= 结果的高 16 位;(ax)= 结果的低 16 位;(cx)= 余数

应用举例:计算 1000000 / 10

在解决除法溢出的问题上,有这样一个公式能够完美的规避掉溢出现象:被除数 / 除数 = (被除数的高 16 位 / 除数)的商 * 65535 + [ (被除数的高 16 位 / 除数)的余数 * 65535 + 被除数的低 16 位 ] / 除数

这个公式将可能会产生溢出的除法运算,转变成了多个不会产生溢出的除法运算。在公式中,等号右边的所有除法运算都可以用 div 指令来完成,并且不会产生溢出现象。

assume cs:code

code segment
start:
    mov dx,128          ;被除数的高 16 位
    mov ax,0            ;被除数的低 16 位
    mov cx,128            ;16 位除数
    call divdw

    mov ah,4ch
    int 21h

;返回参数:商得高16位dx;低16位ax;余数cx
;32 位除法
divdw:
    jmp short divstart
    datareg dw  4 dup (0)
divstart:
    push bx                    ;照常进行寄存器的保护工作
    push ds
    push si

    cmp dx,cx                ;通过这里实现兼容没有溢出的除法运算
    jb divnoflo

    mov bx,cs
    mov ds,bx                ;ds中存放代码段的段地址
    mov si,offset datareg    ;取得自定义数据 datareg 的偏移地址

    mov [si],ax              ;将被除数的低 16 位保存进 datareg 处的第一个字里
    mov ax,dx                ;
    sub dx,dx                ;对 dx 置零, 避免溢出
    div cx                   ;求被除数的高 16 位/除数, 得到商和余数,分别保存在ax和dx当中
    mov [si+2],dx            ;将余数保存进第 datareg 处的第二个字
    mov bx,512                   
   mul bx mov bx,128 mul bx ;将商*65536 , 其中512 * 128 = 65535 mov [si+4],ax ;保存int(H/N)*65536 mov [si+6],dx mov ax,[si+2] ;求得rem(H/N)*65536 mov bx,512 mul bx mov bx,128 mul bx add ax,[si] ;求得rem(H/N)*65536+L div cx ;求得[rem(H/N)*65536+L]/N ***注意这里进行的除法不能清除dx,这里不可能会溢出 mov cx,dx ;求得结果的余数 add ax,[si+4]     ;求得结果的低 16 位 mov dx,[si+6] ;求得结果的低高 16 位 jmp short dsret divnoflo: div cx mov cx,dx sub dx,dx dsret: pop si pop ds pop bx ret code ends end start

三、实现一个子程序,该子程序能将 word 型数据转变为表示十进制数的字符串

子程序代码如下:

dtoc:   push ax
        push si
        push di
        push dx
        push bx
        push cx
        mov di, 0
        mov dx, 0
        mov bx, 10

devide: mov cx, ax         ;将 12666 这个存进寄存器 cx, 在寄存器中它的表现形式是 0011 0001 0111 1010
        jcxz stop
        div bx             ;利用除法来求得 12666 十进制数每一位的值
        inc di
        push dx            ;将这个值存放进栈中
        mov dx, 0
        jmp devide
stop:   mov cx, di      
string: pop bx
        add bx, 30h        ;将每一位的值转换为 ASCII 码的表现形式
        mov [si], bl
        inc si
        loop string

        pop cx
        pop bx
        pop dx
        pop di
        pop si
        pop ax
ret

 

posted @ 2018-11-24 20:22  driveby  阅读(972)  评论(0编辑  收藏  举报