第十章 CALL和RET指令
第十章 CALL和RET指令
10.1 ret和retf
ret用栈中的数据修改IP的内容,实现近转移
ret指令执行过程相当于:pop IP
retf用栈中的数据修改CS和IP中的内容,实现远转移
retf指令执行过程相当于:
pop IP
pop CS
检测点10.1
补全程序,实现从内存1000:0000处开始执行指令。
assume cs:code
stack segment
db 16 dup (0)
stack ends
code segment
start:mov ax,stack
mov ss,ax
mov sp,16
mov ax, 1000h
push ax
mov ax, 0
push ax
retf
code ends
end start
10.2~10.6 call指令
1. 执行call指令时,进行两步操作:
(1) 将当前的IP或CS和IP压入栈中
(2) 转移
call不能实现短转移,其实现转移的方法和jmp指令原理相同
2. 依据位移进行转移的call指令
call 标号
此指令将当前IP压入栈中,并转到标号处执行指令,相当于进行:
push IP
jmp near ptr 标号
在call指令的机器码中没有转移的目的地址,而是位移
注意:读取一条指令后,IP中的值自动增加,再执行读取的指令
检测点10.2
下面的程序执行后,ax中的数值为多少?
内存地址 机器码 汇编指令 执行后情况
1000:0 b8 00 00 mov ax,0 ax=0 ip指向1000:3
1000:3 e8 01 00 call s pop ip ip指向1000:7
1000:6 40 inc ax
1000:7 58 s:pop ax ax=6
用debug进行跟踪确认,“call 标号”是将该指令后的第一个字节偏移地址入栈,再转到标号处执行指令。
3. 转移的目的地址在指令中的call指令
call far ptr 标号
该指令实现段间转移,相当于:
push CS
push IP
jmp far ptr 标号
检测点10.3
下面的程序执行后,ax中的数值为多少?
内存地址 机器码 汇编指令 执行后情况
1000:0 b8 00 00 mov ax,0 ax=0,ip指向1000:3
1000:3 9a 09 00 00 10 call far ptr s pop cs,pop ip,ip指向1000:9
1000:8 40 inc ax
1000:9 58 s:pop ax ax=8h
add ax,ax ax=10h
pop bx bx=1000h
add ax,bx ax=1010h
用debug进行跟踪确认,“call far ptr s”是先将该指令后的第一个字节段地址cs=1000h入栈,再将偏移地址ip=8h入栈,最后转到标号处执行指令。
出栈时,根据栈先进后出的原则,先出的为ip=8h,后出的为cs=1000h
4. 转移地址在寄存器中的call指令
call 16位reg
相当于:
push IP
jmp 16位reg
检测点10.4
下面的程序执行后,ax中的数值为多少?
内存地址 机器码 汇编指令 执行后情况
1000:0 b8 06 00 mov ax,6 ax=6,ip指向1000:3
1000:3 ff d0 call ax pop ip,ip指向1000:6
1000:5 40 inc ax
1000:6 58 mov bp,sp bp=sp=fffeh
add ax,[bp] ax=[6+ds:(fffeh)]=6+5=0bh
用debug进行跟踪确认,“call ax(16位reg)”是先将该指令后的第一个字节偏移地址ip入栈,再转到偏移地址为ax(16位reg)处执行指令。
5. 转移地址在内存中的call指令
(1) call word ptr 内存单元地址
相当于:
push IP
jmp word ptr 内存单元地址
(2) call dword ptr 内存单元地址
相当于:
push CS
push IP
jmp dword ptr 内存单元地址
检测点10.5
(1)下面的程序执行后,ax中的数值为多少?
assume cs:code
stack segment
dw 8 dup (0)
stack ends
code segment
start:mov ax,stack
mov ss,ax
mov sp,16
mov ds,ax
mov ax,0
call word ptr ds:[0eh]
inc ax
inc ax
inc ax
mov ax,4c00h
int 21h
code ends
end start
执行call word ptr ds:[0eh]指令时,先cs入栈,再ip=11入栈,最后ip转移到(ds:[0eh])。(ds:[0eh])=11h,执行inc ax最终ax=3
(2)下面的程序执行后,ax和bx中的数值为多少?
assume cs:codesg
stack segment
dw 8 dup(0)
stack ends
codesg segment
start:
mov ax,stack
mov ss,ax
mov sp,10h
mov word ptr ss:[0],offset s ;(ss:[0])=1ah
mov ss:[2],cs ;(ss:[2])=cs
call dword ptr ss:[0] ;cs入栈,ip=19h入栈,转到cs:1ah处执行指令
;(ss:[4])=cs,(ss:[6])=ip
nop
s: mov ax,offset s ;ax=1ah
sub ax,ss:[0ch] ;ax=1ah-(ss:[0ch])=1ah-19h=1
mov bx,cs ;bx=cs=0c5bh
sub bx,ss:[0eh] ;bx=cs-cs=0
mov ax,4c00h
int 21h
codesg ends
end start
10.7 call和ret的配合使用
子程序的框架:
标号:
指令
ret
具有子程序的源程序框架:
assume cs:code
code segment
main:;主程序
...
call sub1;调用子程序
...
mov ax,4c00h
int 21h
sub1:
...
call sub2;调用子程序
...
ret;返回
sub2:
...
ret;返回
code ends
end
10.8 mul指令
mul指令为乘法指令,用法如下
(1) 两个乘数:要么都是8位,要么都是16位。8位乘法中,一个乘数放在AL中,一个放在8位内存字节单元或8位reg中;16位乘法中,一个乘数放在AX中,另一个放在16位reg或寄存器中.
(2) 结果:8位乘法在AX中,16位乘法在DX和AX中(DX放高16位,AX放低16位)
(3) 格式:
mul reg
mul 内存单元(注意:要用byte ptr和word ptr指出是8位还是16位乘法)
10.9~10.11 模块化设计 & 附注4 用栈传递参数
1.传递参数和返回:使用指定的寄存器或内存单元
2.用栈传递参数
原理:将要传递给子程序的参数压入栈中,子程序从栈中取得参数
举例:计算(a-b)^3,a、b为字型数据
difcube:
push bp
mov bp,sp
mov ax,[bp+4];ax=a[bp+4]=byte a
sub ax,[bp+6];a=a-b [bp+6]=byte b
mov bp,ax
mul bp
mul bp
pop bp
ret 4
指令ret n的含义:
pop ip
add sp,n
由于用栈传递参数,在调用时向栈中压入参数,子程序在返回的时候可以用ret n将栈顶指针恢复。调用以上程序的过程:
mov ax,1
push ax
mov ax,3
push ax
call difcube
此时需注意参数的压栈顺序
示例:C语言编译为汇编
C程序
void add(int,int,int);
int main(void)
{
int a=1;
int b=2;
int c=0;
add(a,b,c);
c++;
}
void add(int a,int b,int c)
{
c=a+b;
}
main()函数不是程序启动后执行的第一个函数,还有别的函数调用main()
局部变量也在栈中存储
汇编程序
main:;int main(void)
mov bp,sp
sub sp,6;给局部变量让出空间
mov word ptr[bp-6],0001;int a=1
mov word ptr[bp-4],0002;int b=2
mov word ptr[bp-2],0000;int c=0
;以上为main()的局部变量a,b,c
push [bp-2]
push [bp-4]
push [bp-6]
;传参
call add;add(a,b,c)
add sp,6;清理调用的函数的局部变量和参数
inc word ptr [bp-2];c++
add:
push bp;备份bp
mov bp,sp
mov ax,[bp+4];AX=a
add ax,[bp+6];AX=a+b
mov [bp+8],ax;c=a+b
mov sp,bp
pop bp;恢复bp
ret
10.12 寄存器冲突的问题
解决方法:在子程序的开始将子程序中所有用到的寄存器中的内容都入栈保存起来,在子程序返回前恢复。
子程序的框架:
子程序:子程序中使用的寄存器入栈
子程序内容
子程序中使用的寄存器出栈
返回(ret\retf)
示例:小写转大写程序:
capital:push cx
push si
change:mov cl,[si]
mov ch,0
jcxz ok
and byte ptr[si],11011111b
inc si
jmp short change
ok:pop si
pop cx
ret
实验10 编写子程序
1. 显示字符串
子程序描述
名称:show_str
功能:在指定的位置使用指定的颜色,显示一个用0结束的字符串
参数:(dh)=行号(取值范围0~24),(dl)=列号(取值范围0~79)
(cl)=颜色,ds:si指向字符串首地址
返回:无
Program ex-10-1 自主解法
assume cs:code,ds:data,ss:stack
data segment
db 'welcome to masm!',0
data ends
stack segment
db 256 dup(0)
stack ends
code segment
start:
mov ax,stack
mov ss,ax
mov sp,256 ;ss:sp=stack:256
mov dh,8 ;row=8
mov dl,3 ;line=3
mov cl,2 ;color=2
mov ax,data
mov ds,ax
mov si,0 ;str=data:0
call show_str
mov ax,4c00h
int 21h
show_str:
;show_str(byte row=(dh),byte line=(dl),
;byte color=(cl),const char* str =(ds:si));
;显存偏移地址offset_display=row*160+line*2
push ax
push bx
push cx
push dx
push si
push di
push es
mov al,dh
mov ah,160
mul ah ;AX=row*160
mov bh,0
mov bl,dl ;BX=(word)line
add ax,bx
add ax,bx ;AX=offset_display=row*160+line*2
mov di,ax ;DI=offset_display
mov ax,0b800h
mov es,ax ;ES=显示缓冲区
mov dl,cl ;DL=color
;es:di 指向显存显示缓冲区的指针
display:
mov ch,0
mov cl,[si] ;CX=(word)(DS:SI)
jcxz ok ;if((cx)==0) return;
mov es:[di],cl ;(ES:DI)=(DS:SI)
mov es:[di+1],dl;(ES:DI+1)=color
add di,2
add si,1
jmp display
ok: pop es
pop di
pop si
pop dx
pop cx
pop bx
pop ax
ret
code ends
end start
Program ex-10-2 参考解法
;效果图
;在屏幕的8行3例,用绿色显示data段中的字符串。
assume cs:code,ds:data
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:
push ax
push bx
push es
push si
mov ax,0b800h
mov es,ax
mov ax,160
mul dh
mov bx,ax;bx=160*dh
mov ax,2
mul dl;ax=dl*2
add bx,ax;mov bx,(160*dh+dl*2)设置es:bx指向显存首地址
mov al,cl;把颜色cl赋值al
mov cl,0
show0:
mov ch,[si]
jcxz show1;(ds:si)=0时,转到show1执行
mov es:[bx],ch
mov es:[bx].1,al
inc si;ds:si指向下一个字符地址
add bx,2;es:bx指向下一个显存地址
jmp show0
show1:
pop si
pop es
pop bx
pop ax
ret
code ends
end start
2. 解决除法溢出的问题
如果结果的商大于AL或AX所能存储的最大值,便会引发CPU的一个内部错误出现除法溢出
子程序描述
名称: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
x:被除数,范围[0,FFFFFFFF]
n:除数,范围[0,FFFF]
h:x高16位
l:x低16位
int()取商
rem()取余
公式的证明:
证明法1:
将计算 X / n 分解为计算:
( H / n ) * 65536 + ( L / n ) ; H = int ( x / 65536 ) ; L = rem ( x / 65536 )
DIV 指令只能得出余数和商,而我们只保留商。余数必然小于除数,一次正确的除法运算只能丢掉一个余数。我们虽然在具体处理时进行了两次除法运算 ( H / n ) 和 ( L / n ) ;但这实质上是一次除法运算(X / n)问题的分解。也就是说,为保证最终结果的正确,两次除法运算只能丢掉一个余数。
在这个问题中,H / n 产生的余数是绝对不能丢的,因为丢掉了它(设为 r )就相当于丢掉了 r * 65536 。
那么如何处理 ( H / n )产生的余数呢?
我们知道:H = int ( H / n) * n + rem ( H / n )
所以有:
( H / n ) * 65536 + ( L / n )
= [ int ( H / n) * n + rem ( H / n ) ] / n * 65536 + L / n
= int ( H / n) * 65536 + rem ( H / n ) * 65536 / n + L / n
= int ( H / n) * 65536 + [ rem ( H / n ) * 65536 + L ] / n
现在讲计算X / n 转化为计算:
int ( H / n) * 65536 + [ rem ( H / n ) * 65536 + L ] / n ;
H = int ( x / 65536 ) ;L = rem ( x / 65536 )
在这里要进行两次除法运算:
第一次: H / n
第二次: [ rem ( H / n ) * 65536 + L ] / n
我们知道第一次不会产生除法溢出。
现证明第二次:
①L ≤ 65535
②rem ( H / n ) ≤ n – 1
由②有:
③rem ( H / n ) * 65536 ≤ ( n – 1 ) *65536
由①,③有:
④rem ( H / n ) * 65536 + L ≤ ( n – 1 ) *65536 + 65535
由④有:
⑤[ rem ( H / n ) * 65536 + L ] / n ≤ [ ( n – 1 ) *65536 + 65535 ] / n
由⑤有:
⑥[ rem ( H / n ) * 65536 + L ] / n ≤ 65536 – ( 1 / n )
所以 [ rem ( H / n ) * 65536 + L ] / n 不会产生除法溢出。
则:X / n = int ( H / n) * 65536 + [ rem ( H / n ) * 65536 + L ] / n ;
H = int ( x / 65536 ) ;L = rem ( x / 65536 )
证明法2:数学证明

Program ex-10-3 自主解法
assume cs:code,ds:data,ss:stack
data segment
db 256 dup(0)
data ends
stack segment
db 256 dup(0)
stack ends
code segment
start:
mov ax,stack
mov ss,ax ;ss=stack
mov sp,256
mov ax,data
mov ds,ax ;ds=data
mov ax,4240h;L(16bit)
mov dx,000fh;H(16bit)
mov cx,0ah ;N(16bit)
;计算1000000/10(F4240h/0ah)
call divdw
mov ax,4c00h
int 21h
divdw:
push si
mov si,0
mov [si],ax ;ds:0=l
mov [si+2],dx ;ds:2=h
mov [si+4],cx ;ds:4=n
;x/n=int(h/n)*65536+[rem(h/n)*65536+l]/n
;计算int(h/n)
;不要乘以65536!直接作为一个双子数据高16位处理
;int(h/n)是结果得高16位
mov ax,[si+2]
mov dx,0
div word ptr[si+4]
mov [si+6],ax ;int(h/n) 商
mov [si+8],dx ;rem(h/n) 余数
;计算[rem(h/n)*65536+l]/n
mov ax,[si]
;(dx*0FFFFh+ax)=rem(h/n)*65536+l
div word ptr[si+4]
;ax=[rem(h/n)*65536+l]/n,为结果的低16位
;dx为余数,移入cx
mov cx,dx
mov dx,[si+6]
pop si
ret
code ends
end start
Program ex-10-4参考解法
效果图
;应用举例:计算1000000/10(f4240h/0ah)
assume cs:code
code segment
start:mov ax,4240h
mov dx,000fh
mov cx,0ah
call divdw
mov ax,4c00h
int 21h
divdw:
push si
push bx
push ax
mov ax,dx
mov dx,0
div cx;被除数的高位/cx,高位在ax,余数在dx
mov si,ax
pop ax
div cx;(被除数高位的商+低位)/cx,高位在ax,余数在dx
mov cx,dx;余数入cx
mov dx,si;高位的商入dx
pop bx
pop si
ret
code ends
end start
公式x/n=int(h/n)*65536+[rem(h/n)*65536+l]/n解析:
如果高位除法有商,那么商就是结果的高位值,如果会有余数,那么余数自然不能丢弃,余数就作为低位除法的dx(也就是高位的被除数,因为他是从高位除法中余下的)
做低位除法的时候,就拿余下的dx+低位数的ax除以除数,会得到 一个低位的商 和 余数
高位的商+低位数的商+余数 就是结果
3. 数值显示
编程,将data段中的数据以十进制形式显示
子程序描述
名称: dtoc
功能:将word型数据转变为表示十进制形式的字符串,字符串以0结尾
参数:(ax)=word型数据
ds:si指向字符串的首地址
返回:无
提示与分析
十进制数码字符对应的ASCII码=十进制数码值+30H
或者用数码值+’0’也可以
得出10进制数每位的值的方法
注:这与10进制数转换为其他进制的方法类似,该用法相当于用10进制转换10进制
注意出来的余数应该倒过来才是原来的数字
使用C++演示原理:
#include<iostream>
using namespace std;
int main()
{
int a=12666;
while(a!=0)
{
cout<<(char)(a%10+'0')<<" ";
a/=10;
}
return 0;
}
结果:6 6 6 2 1
Program ex-10-5 自主解法
assume cs:code,ss:stack,ds:data
stack segment
db 256 dup(0)
stack ends
data segment
db 256 dup(0)
data ends
code segment
start:
mov ax,stack
mov ss,ax
mov sp,256
mov ax,data
mov ds,ax
mov si,0
mov ax,12666
call dtoc
mov cl,2
mov dh,8
mov dl,3
call show_str
mov ax,4c00h
int 21h
dtoc:
;void dtoc(word data=(ax),const char* str=(ds:si));
push dx
push cx
push bx
push ax
push si
push di
mov di,si ;si初值
mov bx,10
mov dx,0
;注意直接除会溢出!
dt_l1:
mov dx,0
div bx ;ax=ax/10,dx=ax%10;
add dl,'0' ;dl+='0';
mov [si],dl ;ds:si=dl
mov cx,ax
inc si
jcxz exit_dtoc
jmp dt_l1
exit_dtoc:
;反转字符串
mov ax,si
sub ax,di ;ax=si-di
mov cx,ax ;ax=字符数
reverse_str:
;由于push和pop操作为字操作
;因此要将每个字符从字节转为字
mov bx,[di]
mov bh,0
push bx
inc di
loop reverse_str
sub si,ax ;将si恢复为初始值
mov cx,ax
copy_str:
pop bx
mov [si],bl
inc si
loop copy_str
pop di
pop si
pop ax
pop bx
pop cx
pop dx
ret
show_str:
......
code ends
end start
Program ex-10-6 参考解法
assume cs:code,ds:data
data segment
db 10 dup (0)
data ends
code segment
start:mov ax,12666
mov bx,data
mov ds,bx
mov si,0;ds:si指向data首地址
call dtoc1
mov dh,8
mov dl,3
mov cl,2
call show_str
mov ax,4c00h
int 21h
dtoc1:push ax
push bx
push cx
push dx
push si
push di
mov di,0
d10:mov dx,0;设置被除数高位为0
mov bx,10;除数为10
div bx
add dx,30h;ax/10的余数+30h,转为字符
push dx;字符入栈
inc di;记录字符个数
mov cx,ax
jcxz d11;当ax/10的商=0时,转到d11执行
jmp d10
d11:mov cx,di
d12:pop dx;字符出栈
mov [si],dl
inc si;ds:si指向下一单元
loop d12
mov dl,0
mov [si],dl;设置结尾符0
pop si
pop di
pop dx
pop cx
pop bx
pop ax
ret
show_str:
push ax
push bx
mov ax,0b800h
mov es,ax
mov ax,160
mul dh
mov bx,ax;bx=160*dh
mov ax,2
mul dl;ax=dl*2
add bx,ax;mov bx,(160*dh+dl*2)设置es:bx指向显存首地址
mov al,cl;把颜色cl赋值al
mov cl,0
show0:
mov ch,[si]
jcxz show1;(ds:si)=0时,转到show1执行
mov es:[bx],ch
mov es:[bx].1,al
inc si;ds:si指向下一个字符地址
add bx,2;es:bx指向下一个显存地址
jmp show0
show1:
pop bx
pop ax
ret
code ends
end start
改进程序:将dword型数据转为字符
名称:dtoc2
功能:将dword型数据转变为表示十进制的字符串,字符串以0为结尾符。
参数:(ax)=dword型数据的低16位;
(dx)=dword型数据的高16位;
ds:si指向字符串首地址。
返回:无。
Program ex-10-7 自主解法
assume cs:code,ss:stack,ds:data
stack segment
db 256 dup(0)
stack ends
data segment
db 256 dup(0)
data ends
code segment
start:
mov ax,stack
mov ss,ax
mov sp,256
mov ax,data
mov ds,ax
mov si,0
;注意这里不要用10进制写!
mov ax,0bf52h
mov dx,1h
call dtoc2
mov cl,2
mov dh,8
mov dl,3
call show_str
mov ax,4c00h
int 21h
dtoc2:
;void dtoc2(dword data=(bx)*65536+(ax),const char* str=(ds:si));
push dx
push cx
push bx
push ax
push si
push di
mov di,si ;si初值
dt_l1:
mov cx,10
call divdw
;dx*65536+ax=商,cx=余数
add cl,'0' ;cl+='0';
mov [si],cl ;ds:si=cl
mov cx,dx
jcxz dx_is_0 ;dx==0时继续判断ax是否为0
jmp dx_is_not_0 ;dx!=0时
dx_is_0:
mov cx,ax
dx_is_not_0:
inc si
jcxz exit_dtoc ;ax==0时向外跳转,否则向下
jmp dt_l1
exit_dtoc:
;反转字符串
mov ax,si
sub ax,di ;ax=si-di
mov cx,ax ;ax=字符数
reverse_str:
;由于push和pop操作为字操作
;因此要将每个字符从字节转为字
mov bx,[di]
mov bh,0
push bx
inc di
loop reverse_str
sub si,ax ;将si恢复为初始值
mov cx,ax
copy_str:
pop bx
mov [si],bl
inc si
loop copy_str
pop di
pop si
pop ax
pop bx
pop cx
pop dx
ret
show_str:
......
divdw:
push si
mov si,0
;为了防止影响到其他程序的内存,将使用的单元备份
;解决内存冲突的方法:
push [si]
push [si+2]
push [si+4]
push [si+6]
push [si+8]
mov [si],ax ;ds:0=l
mov [si+2],dx ;ds:2=h
mov [si+4],cx ;ds:4=n
;x/n=int(h/n)*65536+[rem(h/n)*65536+l]/n
;计算int(h/n)
;不要乘以65536!直接作为一个双子数据高16位处理
;int(h/n)是结果得高16位
mov ax,[si+2]
mov dx,0
div word ptr[si+4]
mov [si+6],ax ;int(h/n) 商
mov [si+8],dx ;rem(h/n) 余数
;计算[rem(h/n)*65536+l]/n
mov ax,[si]
;(dx*0FFFFh+ax)=rem(h/n)*65536+l
div word ptr[si+4]
;ax=[rem(h/n)*65536+l]/n,为结果的低16位
;dx为余数,移入cx
mov cx,dx
mov dx,[si+6]
pop [si+8]
pop [si+6]
pop [si+4]
pop [si+2]
pop [si]
pop si
ret
code ends
end start
Program ex-10-8 参考解法
;应用举例:将数据2494967295以十进制的形式在屏幕的8行3列,用绿色显示出来。
assume cs:code,ds:data
data segment
db 10 dup (0)
data ends
code segment
start:mov ax,0ffffh
mov dx,0ffffh
mov bx,data
mov ds,bx
mov si,0;ds:si指向字符串首地址
call dtoc2
mov dh,8
mov dl,3
mov cl,2
call show_str
mov ax,4c00h
int 21h
dtoc2:
push ax
push bx
push cx
push dx
push si
push di
mov di,0
d20:mov cx,10;除数为10
call divdw
add cx,30h;余数+30h,转为字符
push cx;字符入栈
inc di;记录字符个数
mov cx,ax
jcxz d21;低位商=0时,转到d21检测高位商
jmp d20
d21:mov cx,dx
jcxz d22;高低位商全=0时,转到d22执行
jmp d20
d22:mov cx,di
d23:pop ax;字符出栈
mov [si],al
inc si;ds:si指向下一单元
loop d23
mov al,0
mov [si],al;设置结尾符0
pop di
pop si
pop dx
pop cx
pop bx
pop ax
ret
show_str:
push ax
push bx
mov ax,0b800h
mov es,ax
mov ax,160
mul dh
mov bx,ax;bx=160*dh
mov ax,2
mul dl;ax=dl*2
add bx,ax;mov bx,(160*dh+dl*2)设置es:bx指向显存首地址
mov al,cl;把颜色cl赋值al
mov cl,0
show0:
mov ch,[si]
jcxz show1;(ds:si)=0时,转到show1执行
mov es:[bx],ch
mov es:[bx].1,al
inc si;ds:si指向下一个字符地址
add bx,2;es:bx指向下一个显存地址
jmp show0
show1:
pop bx
pop ax
ret
divdw:
push si
push bx
push ax
mov ax,dx
mov dx,0
div cx;被除数的高位/cx
mov si,ax
pop ax
div cx;(被除数高位的商+低位)/cx
mov cx,dx;余数入cx
mov dx,si;高位的商入dx
pop bx
pop si
ret
code ends
end start
课程设计1
将实验7中的Power idea公司的数据按照图所示的格式在屏幕上显示现来。
效果图
Program lesson-design-1-1 自己的解法
assume cs:code,ds:data,ss:stack,es:table_
data segment
db '1975','1976','1977','1978','1979','1980','1981','1982','1983'
db '1984','1985','1986','1987','1988','1989','1990','1991','1992'
db '1993','1994','1995'
;以上为21年的21个字符串(+84byte)
dd 16,22,382,1356,2390,8000,16000,24486,50065,97479,140417,197514
dd 345980,590827,803530,1183000,1843000,2759000,3753000,4649000,5937000
;以上为21年的总收入数据(+168byte)
dw 3,7,9,13,28,38,130,220,476,778,1001,1442,2258,2793,4037,5635,8226
dw 11452,14430,15257,17800
;以上为21年每年的雇员人数
data ends
temp segment
db 512 dup(0)
temp ends
stack segment
db 256 dup(0)
stack ends
table_ segment
db 21 dup ('year',0,'summ ne ?? ')
table_ ends
code segment
start:
mov ax,data
mov ds,ax ;ds=data
mov ax,table_
mov es,ax ;es=table_
mov ax,stack
mov ss,ax ;ss=stack
mov sp,256
mov bx,0
mov cx,21
mov di,0
;写入年份
l1:
mov ax,[di]
mov es:[bx+0],ax
mov ax,[di+2]
mov es:[bx+2],ax
add di,4
;换下一行
add bx,16
loop l1
;写入收入
mov bx,0
mov cx,21
l2:
mov ax,[di]
mov es:[bx+5],ax
mov ax,[di+2]
mov es:[bx+7],ax
add di,4
add bx,16
loop l2
;写入雇员数
mov bx,0
mov cx,21
l3:
mov ax,[di]
mov es:[bx+0ah],ax
add di,2
add bx,16
loop l3
;计算人均收入
mov bx,0
mov cx,21
mov di,84
mov si,0 ;用于定位被除数
l4:
mov dx,[di+2]
mov ax,[di]
div word ptr[168+si]
mov es:[bx+0dh],ax
add si,2
add di,4
add bx,16
loop l4
;显示table
mov ax,table_
mov ds,ax ;ds=table_
mov si,0 ;表格每一行的地址
mov cx,21
mov dh,2 ;行号
s1:
mov dl,20 ;每次都要还原列号
push cx ;显示年份
mov cl,2
call show_str ;str=ds:si
pop cx
add si,10h ;表格下一行
inc dh ;下一行
loop s1
mov ax,temp
mov ds,ax ;ds=temp
mov si,0 ;ds:si存放字符
mov di,0 ;es:di取数据
mov cx,21
mov si,0
s2:
;将收入转为字符
mov ax,es:[di+5]
mov dx,es:[di+7]
call dtoc2
;字符位于ds:0
;将雇员数转为字符
add si,10
mov ax,es:[di+0ah]
mov dx,0
call dtoc2
;字符位于ds:10
;将人均收入转为字符
add si,6
mov ax,es:[di+0dh]
mov dx,0
call dtoc2
add si,6
add di,16
loop s2
mov dh,2 ;行号
mov dl,28 ;列号
mov cx,21
mov si,0
s3: ;打印字符
mov dl,28
push cx
mov cl,2
call show_str
pop cx
add si,10
add dl,10
push cx
mov cl,2
call show_str
pop cx
add si,6
add dl,6
push cx
mov cl,2
call show_str
pop cx
add si,6
add dl,6
inc dh
loop s3
mov ax,4c00h
int 21h
dtoc2:
;void dtoc2(dword data=(dx)*65536+(ax),const char* str=(ds:si));
push dx
push cx
push bx
push ax
push si
push di
mov di,si ;si初值
dt_l1:
mov cx,10
call divdw
;dx*65536+ax=商,cx=余数
add cl,'0' ;cl+='0';
mov [si],cl ;ds:si=cl
mov cx,dx
jcxz dx_is_0 ;dx==0时继续判断ax是否为0
jmp dx_is_not_0 ;dx!=0时
dx_is_0:
mov cx,ax
dx_is_not_0:
inc si
jcxz exit_dtoc ;ax==0时向外跳转,否则向下
jmp dt_l1
exit_dtoc:
;反转字符串
mov ax,si
sub ax,di ;ax=si-di
mov cx,ax ;ax=字符数
reverse_str:
;由于push和pop操作为字操作
;因此要将每个字符从字节转为字
mov bx,[di]
mov bh,0
push bx
inc di
loop reverse_str
sub si,ax ;将si恢复为初始值
mov cx,ax
copy_str:
pop bx
mov [si],bl
inc si
loop copy_str
pop di
pop si
pop ax
pop bx
pop cx
pop dx
ret
show_str:
;void show_str(byte row=(dh),byte line=(dl),
;byte color=(cl),const char* str =(ds:si));
;显存偏移地址offset_display=row*160+line*2
push ax
push bx
push cx
push dx
push si
push di
push es
mov al,dh
mov ah,160
mul ah ;AX=row*160
mov bh,0
mov bl,dl ;BX=(word)line
add ax,bx
add ax,bx ;AX=offset_display=row*160+line*2
mov di,ax ;DI=offset_display
mov ax,0b800h
mov es,ax ;ES=显示缓冲区
mov dl,cl ;DL=color
;es:di 指向显存显示缓冲区的指针
display:
mov ch,0
mov cl,[si] ;CX=(word)(DS:SI)
jcxz ok ;if((cx)==0) return;
mov es:[di],cl ;(ES:DI)=(DS:SI)
mov es:[di+1],dl;(ES:DI+1)=color
add di,2
add si,1
jmp display
ok: pop es
pop di
pop si
pop dx
pop cx
pop bx
pop ax
ret
divdw:
push si
mov si,0
;为了防止影响到其他程序的内存,将使用的单元备份
;解决内存冲突的方法:
push [si]
push [si+2]
push [si+4]
push [si+6]
push [si+8]
mov [si],ax ;ds:0=l
mov [si+2],dx ;ds:2=h
mov [si+4],cx ;ds:4=n
;x/n=int(h/n)*65536+[rem(h/n)*65536+l]/n
;计算int(h/n)
;不要乘以65536!直接作为一个双子数据高16位处理
;int(h/n)是结果得高16位
mov ax,[si+2]
mov dx,0
div word ptr[si+4]
mov [si+6],ax ;int(h/n) 商
mov [si+8],dx ;rem(h/n) 余数
;计算[rem(h/n)*65536+l]/n
mov ax,[si]
;(dx*0FFFFh+ax)=rem(h/n)*65536+l
div word ptr[si+4]
;ax=[rem(h/n)*65536+l]/n,为结果的低16位
;dx为余数,移入cx
mov cx,dx
mov dx,[si+6]
pop [si+8]
pop [si+6]
pop [si+4]
pop [si+2]
pop [si]
pop si
ret
code ends
end start
Program lesson-design-1-2 参考解法
assume cs:code,ds:data,es:table
table segment
db '1975','1976','1977','1978','1979','1980','1981','1982','1983'
db '1984','1985','1986','1987','1988','1989','1990','1991','1992'
db '1993','1994','1995'
dd 16,22,382,1356,2390,8000,16000,24486,50065,97479,140417,197514
dd 345980,590827,803530,1183000,1843000,2795000,3753000,4649000,5937000
dw 3,7,9,13,28,38,130,220,476,778,1001,1442,2258,2793,4037,5635,8226
dw 11542,14430,15257,17800
dw 5,3,42,104,85,210,123,111,105,125,140,136,153,211,199,209,224,239
dw 260,304,333
table ends
data segment
db 32 dup (0)
data ends
code segment
start:mov ax,data
mov ds,ax
mov ax,table
mov es,ax
mov bx,0
mov si,0
mov di,0
mov cx,21
mov dh,2
mov dl,30
g:push cx
push dx
mov ax,es:[bx]
mov [si],ax
mov ax,es:[bx].2
mov [si].2,ax;年份入ds:si
add si,6
mov ax,es:[bx].84
mov dx,es:[bx].86
call dtoc2;收入转成十进制字符入ds:si
add si,10
mov ax,es:[di].168
mov dx,0
call dtoc2;人员数转成十进制字符入ds:si
add si,6
mov ax,es:[di].210
mov dx,0
call dtoc2;人均收入转成十进制字符入ds:si
mov si,0;设置ds:si指向需显示字符首地址
b:mov cx,29
c:push cx
mov cl,[si]
jcxz f;(ds:si)=0转到f执行
d:inc si
pop cx
loop c
inc si
mov al,0
mov [si],al;设置结尾符0
mov si,0;设置ds:si指向需显示字符首地址
pop dx
mov cl,2
call show_str
add bx,4;dword数据指向下一数据单元
add di,2;word数据指向下一数据单元
add dh,1;指向显存下一行
pop cx
loop g
mov ax,4c00h
int 21h
f:mov al,20h
mov [si],al;(ds:si)=0的数据改成空格
jmp d
dtoc2:
push ax
push bx
push cx
push dx
push si
push di
mov di,0
d20:mov cx,10;除数为10
call divdw
add cx,30h;余数+30h,转为字符
push cx;字符入栈
inc di;记录字符个数
mov cx,ax
jcxz d21;低位商=0时,转到d21检测高位商
jmp d20
d21:mov cx,dx
jcxz d22;高低位商全=0时,转到d22执行
jmp d20
d22:mov cx,di
d23:pop ax;字符出栈
mov [si],al
inc si;ds:si指向下一单元
loop d23
mov al,0
mov [si],al;设置结尾符0
pop di
pop si
pop dx
pop cx
pop bx
pop ax
ret
divdw:
push si
push bx
push ax
mov ax,dx
mov dx,0
div cx;被除数的高位/cx
mov si,ax
pop ax
div cx;(被除数高位的商+低位)/cx
mov cx,dx;余数入cx
mov dx,si;高位的商入dx
pop bx
pop si
ret
show_str:
push ax
push bx
push es
push si
mov ax,0b800h
mov es,ax
mov ax,160
mul dh
mov bx,ax;bx=160*dh
mov ax,2
mul dl;ax=dl*2
add bx,ax;mov bx,(160*dh+dl*2)设置es:bx指向显存首地址
mov al,cl;把颜色cl赋值al
mov cl,0
show0:
mov ch,[si]
jcxz show1;(ds:si)=0时,转到show1执行
mov es:[bx],ch
mov es:[bx].1,al
inc si;ds:si指向下一个字符地址
add bx,2;es:bx指向下一个显存地址
jmp show0
show1:
pop si
pop es
pop bx
pop ax
ret
code ends
end start
