一、call和ret指令
call和ret都是转移指令,经常被用来实现子程序的设计。
ret和retf
ret指令用栈中的数据修改IP的内容,实现段内近转移;相当于pop ip
retf指令用栈中的数据修改CS和IP的内容,实现段间转移;相当于pop ip,pop cs
call指令
格式:call 标号 相当于push ip,jump near ptr 标号
格式:call far ptr 标号 相当于push cs,pushi ip,jump far ptr 标号
注意:这里先push cs,在push ip,原因很简单,先push的cs是存放在内存的高地址的,正好与retf指令对应,它先取出ip,再取出cs,与call入栈的顺序正好相反。
可以利用call和ret指令来实现子程序的控制。子程序的框架如下:
标号A:
指令
ret
使用时call 标号A即可。
二、mul指令
mul是乘法指令。
两个相乘的数:要么都是8位,要么都是16位;
如果是8位,一个默认存放在AL中,另一个放在8为reg或内存字节单元中;
如果是16位,则一个默认在AX中,另一个放在16位reg或内存字单元中。
对于8位乘法,结果默认存放在AX中,对于16位乘法,结果高位在DX中,低位在AX中。
三、模块化程序设计
实例1:计算data段第一组数据的三次方,结果存放在后面一组dword单元中。
assume cs:code,ds:data
data segment
dw 1,2,3,4,5,6,7,8
dd 8 dup(0)
data ends
code segment
start: mov ax,data
mov ds,ax
mov di,0 ;ds:di连续的两个字节存放输入数
mov si,10h ;ds:si连续的四个字节存放输出结果
mov cx,8 ;循环次数8次
s: mov bx,[di] ;将输入数存放在bx中
call cube ;条用cube子程序
mov [si],ax ;将计算结果保留,ax存放低位
mov [si+2],dx ;将计算结果保留,dx存放高位
add di,2 ;计算数内存地址加2
add si,4 ;计算结果内存地址加4
loop s
;cube字程序实现计算N的三次方的功能
;输入N存放在bx中
;输出存放在DX,AX中
cube: mov dx,0 ;存放输出结果的高位DX需置零
mov ax,bx ;将一个乘数存放在AX中
mul bx ;乘以bx中的N
mul bx ;在乘以N(注:这里N*N的结果有可能已经超过16位,本程序忽略了这种情况)
ret ;返回程序
mov ax,4c00h
int 21h
code ends
end start
实例2:通过子程序实现在指定位置,指定颜色,显示一个以0结束的字符串
代码如下
assume cs:code,ds:data,ss:stack
data segment
db 'welcome to masm!',0
data ends
stack segment
db 10 dup(0)
stack ends
code segment
start: mov ax,data
mov ds,ax ;数据段初始化
mov ax,stack
mov ss,ax
mov sp,10 ;栈段初始化
mov si,0
mov dh,13
mov dl,35
mov cl,2
call show_str ;在13行,39列,以红色字体显示字符串
mov ax,4c00h
int 21h
;在屏幕上显示字符串
;dh存储行号,范围0~24,dl存储列号,范围0~79
;cl存储颜色
;ds:si指向字符串的首地址,字符串以0结束
show_str: push ax
push cx
push es
push di ;该子程序将要使用的寄存器入栈
mov al,0a0h
mul dh ;caculate dh*a0h,result in ax ax = dh * a0h
mov dh,0
add ax,dx
add ax,dx ;ax = ax + 2 * dl, now we get the first position to display the string
mov dx,0b800h
mov es,dx ;初始化es为显存的段地址
mov di,ax ;es:di表示显存中首个需要显示的字符串的地址,di=di+2依次显示其余的字符
mov al,cl ;将颜色值存储到al中,因为下面需要使用cx寄存器
display: mov cl,[si]
mov ch,0 ;将需要显示的字符读到cx中
jcxz ok ;如果该字符为0,则转移到ok标号处,结束程序
mov es:[di],cl ;既然该字符不为0,那么就将该字符写到es:di中
mov es:[di+1],al ;将颜色值写到es:[di+1]中
add di,2
inc si
jmp short display
ok: pop di
pop es
pop cx
pop ax ;依次将寄存器出栈
ret
code ends
end start
实例3:除法的溢出问题
mov ax,1000
mov bh,1
div bh
上面这个简单的程序,在编译过程中不会报错,但是在执行过程中会报错:divide overflow,也就是除法溢出。
原因很好理解,我们都知道计算结果是1000,商应该保存在al寄存器中,可以al是8位寄存器,不能保存这么大的数,这就造成了溢出的问题。
编写子程序,能够实现不会溢出的div运算(所谓不会溢出,其实就是用更多位的寄存器来保存计算结果而已)
assume cs:code,ds:data
code segment
;功能:进行不会产生溢出的除法运算,被除数为dword型,除数为word型,结果为dword型
;输入:ax,被除数低位;dx,被除数高位;cx,除数
;输出:ax,商的低位;dx,商的高位;cx,余数
divdw: mov bx,ax ;保存ax的值到bx中
mov ax,dx
mov dx,0
div cx ;计算dx/cx,商在ax中,余数在dx中,这个除法运算一定不会产生溢出
push ax ;将计算的商保存到栈中,它将作为最终结果的高位保存在dx中
mov ax,bx ;此时的被除数:dx/cx的余数放在dx中,传入此子程序的被除数的低位在ax中
div cx
mov cx,dx ;保存最终结果的余数
pop dx ;最终结果的商的高位
ret
start: mov ax,4240h
mov dx,0fh
mov cx,0ah ;计算1000000/10(f4240h/a0h)
call divdw
mov ax,4c00h
int 21h
code ends
end start
实例4:
将12666以十进制的形式显示在8行3列上,绿色显示
assume cs:code,ds:data,ss:stack
stack segment
db 10h dup(0)
stack ends
code segment
mov ax,stack
mov ss,ax
mov sp,10h
start: mov ax,12666
call dtoc
mov dh,13
mov dl,35
mov cl,2
call show_str
mov ax,4c00h
int 21h
;功能:将word型数据转换为十进制字符串,并以0结束
;word型数据存放在ax中
;输出字符串保存在ds:si为首的内存中
dtoc: mov si,0
s0: inc si
mov dx,0
mov bx,10
div bx
;div byte ptr 10
;mov dh,ah
;mov dl,0
add dx,30h
push dx
;mov ah,0
mov cx,ax
jcxz ok1
jmp short s0
ok1: mov cx,si
mov si,0
s1: pop ax
mov [si],al
inc si
loop s1
mov byte ptr [si],0
mov si,0
ret
;在屏幕上显示字符串
;dh存储行号,范围0~24,dl存储列号,范围0~79
;cl存储颜色
;ds:si指向字符串的首地址,字符串以0结束
show_str: push ax
push cx
push es
push di ;该子程序将要使用的寄存器入栈
mov al,0a0h
mul dh ;caculate dh*a0h,result in ax ax = dh * a0h
mov dh,0
add ax,dx
add ax,dx ;ax = ax + 2 * dl, now we get the first position to display the string
mov dx,0b800h
mov es,dx ;初始化es为显存的段地址
mov di,ax ;es:di表示显存中首个需要显示的字符串的地址,di=di+2依次显示其余的字符
mov al,cl ;将颜色值存储到al中,因为下面需要使用cx寄存器
display: mov cl,[si]
mov ch,0 ;将需要显示的字符读到cx中
jcxz ok ;如果该字符为0,则转移到ok标号处,结束程序
mov es:[di],cl ;既然该字符不为0,那么就将该字符写到es:di中
mov es:[di+1],al ;将颜色值写到es:[di+1]中
add di,2
inc si
jmp short display
ok: pop di
pop es
pop cx
pop ax ;依次将寄存器出栈
ret
code ends
end start