一、编写一个通用的子程序来实现显示字符串的功能
名称: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