call 和 ret 都是可以改变 ip 或是 cs 和 ip 。经常用来实现子程序设计。
10.1 ret 和 retf
ret指令用栈中的数据,修改IP实现近转移
retf指令用栈中的程序,修改call和ret内容实现远转移。
CPU执行ret指令时:
pop ip
CPU执行retf指令时:
pop ip
pop cs
所以压栈的时候是先压 cs,再压 ip
检测点10.1
mov ax,1000h
mov ax,0
10.2 call 指令
CPU执行call时候,
- 压入 ip 或 cs 和 ip。
- 转移
cal不能实现短转移,并且原理和jmp相同
10.3 依据位移进行转移的 call 指令
- 将当前sp压栈,push ip ; sp-1,放入ip
- IP = IP + 16位位移地址
16位位移 = 目标地址-call指令后一个指令的初始地址。;和 jmp near ptr 标号 类似。
检测点 10.2
下面的程序执行后,ax中的数值为多少?
当 call s 的时候,此时的 CS:IP指向的是 1000:6,所以将 此时的 IP=6 压入栈中。而后再 1000:7的时候,pop 出 6 给 ax。所以 ax最后的数值为 6。
10.4 转移的目的地址在指令中的call指令
call far ptr 标号,实现的是段间转移。
-
push cs
push ip -
cs = 标号所在段的段地址
ip = 标号所在的偏移地址
其实相当于 push cs, push ip,jmp far ptr 标号。
检测点 10.3
先将 CS:IP 压入栈中 push 1000, push 8
pop ax ; ax = 8
add ax,ax; ax = 16
pop bx; bx = 1000
add ax,bx ; ax = 1016
所以ax 最后的值为1016.
10.4 转移地址在寄存器中的 call 指令
指令格式: call 16 位 reg
push ip
jmp16位 reg ;ip = 16位 reg
检测点 10.4
mov ax,6 ;ax = 6
call ax; push 5 ,jmp ax
mov bp,sp;
add ax,[bp]; 6 + 5 =11
所以最后ax = 11
10.6 转移地址在内存中的 call 指令
- call word ptr 内存单元地址
push ip
jmp word ptr 内存单元地址 - call dword ptr 内存单元地址
push cs
push ip
jmp dword ptr 内存单元地址 ; 例如 ds:[0]是cs,ds:[2]是ip
检测点 10.5
call word ptr ds:[0EH] ;将IP = 0011h push栈中,也就是在0EH位置上。ds 和 ss 指向同一个段。所以 ds:[0EH]内容也就是 刚刚才压入的下一条指令的地址。所以继续执行。
所以到了mov ax,4c00h前,ax为3.
ax = 0, bx = 0
10.7 call 和 ret 的配合使用
bx = 1
ax = 1+1=2, ax = 4, ax=8
mov bx,ax,所以 bx= 8
10.8 mul 指令
两个都是8位: 最大255
al * 8位reg / 内存单元=》ax
两个都是16位
ax*16位reg / 内存单元=》 dx ax
格式:
mul reg
mul 内存单元
10.9 模块化程序设计
call 和 ret 指令共同支持了汇编语言编程中的模块化设计。
10.10 参数和结果传递的问题
- 将参数存储在什么地方
- 计算得到的数值,存储在什么地方?
通过寄存器
assume cs:code
data segment
dw 1,2,3,4,5,6,7,8
dd 0,0,0,0,0,0,0,0
data ends
code segment
start :
mov ax,data
mov ds,ax
mov si,0
mov di,16
mov cx,8
s: mov bx,[si]
call cube
mov [di],ax
mov [di].2,dx
add si,2
add di,4
loop s
mov ax,4c00h
int 21h
cube:
mov ax,bx
mul bx
mul bx
ret
code ends
end start
10.11 批量数据的传递
当需要传入一个参数,两个参数时,我们勉强还可以用寄存器来存。但如果是10个,20个呢?
所以在这种时候,我们要将参数传入内存中。
例如,将字符串全部大写化:
assume cs:code
data segment
db 'conversation'
data ends
code segment
start:
mov ax,data
mov si,0
mov cx,12
call capital
mov ax,4c00h
int 21h
; void capital (si, cx)
capital: and byte ptr [si],11011111b
inc si
loop capital
ret
code ends
end start
10.12 寄存器冲突的问题
以上的程序如果在数据段中:
data segment
db 'helloworld',0
data ends
这样只传入si
;void capital (si)
capital :
mov cl,[si]
mov ch,0
jcxz ok
and byte ptr [si],11011111b
inc si
jmp short capital
ok :ret
因为是CX判断是否为0跳转,所以如果在外面套上 标号 和 loop 标号,就会出现混乱。
问题10.2
cx 的使用出现了混乱,主程序与子程序共同使用了cx。
解决:
- 换其他寄存器
- 不要使用其他会产生冲突的寄存器。
所以说不如直接换用栈来存储。
实验10 编写子程序
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
; void show_str (dh,dl,cl,ds:si)
show_str:
mov ax,0b800h
mov es,ax ;要显示的起始段 es
mov al,160
mul dh
mov dh,0
add dx,dx
add ax,dx
mov di,ax ;要显示的起始位置 di
mov ah,cl
s: mov ch,0
mov cl,[si]
jcxz s0
mov ch,ah; 字符所在寄存器 cx
mov es:[di],cx
add di,2
inc si
jmp s
s0: ret
code ends
end start
运行结果:
assume cs:code
code segment
start:
mov ax,4240h
mov dx,000Fh
mov cx,000Ah
call divdw
mov ax,4c00h
int 21h
divdw: ;dx,ax,cx divdw(dx,ax,cx)
mov di,dx
mov si,ax
;X/N = int(H/N)*65536+[rem(H/N)*65536+L]/N
;=左边+右边
mov ax,dx ;右边
mov dx,0h
div cx
mov ax,si
div cx ;dx,ax
mov bx,dx ;16余
mov si,ax ;低16商
mov ax,di ;左边
mov dx,0
div cx
mov dx,ax ;高16商
mov ax,si ;低16商
mov cx,bx ;16余
ret
code ends
end start
运行结果:
;X/N = int(H/N)*65536+[rem(H/N)*65536+L]/N = 左+右
这个公式中的65536是 1 0000h,也就是将ax放到dx中
所以,左边得到的就是高16位的商。右边多了/L,得到的余数就是最终的余数,得到的商就是低16位的商。
assume cs:code,ds:data
data segment
db 10 dup(0)
data ends
stack segment
db 128 dup(0)
stack ends
code segment
start:
mov ax,stack
mov ss,ax
mov sp,16
mov ax,12666
mov bx,data
mov ds,bx
mov si,0
call dtoc
mov dh,8
mov dl,3
mov cl,2
mov di,0
mov si,0
call show_str
mov ax,4c00h
int 21h
show_str: ;函数
mov ax,0b800h
mov es,ax ;要显示的起始段 es
mov al,160
mul dh
mov dh,0
add dx,dx
add ax,dx
mov di,ax ;要显示的起始位置 di
mov ah,cl
s2: mov ch,0
mov cl,[si]
jcxz s3
mov ch,ah; 字符所在寄存器 cx
mov es:[di],cx
add di,2
inc si
jmp s2
s3: ret
divdw: ;dx,ax,cx divdw(dx,ax,cx) 商h商l 余数
push bx
push di
push si
mov di,dx
mov si,ax
;X/N = int(H/N)*65536+[rem(H/N)*65536+L]/N
;=左边+右边
mov ax,dx ;右边
mov dx,0h
div cx
mov ax,si
div cx ;dx,ax
mov bx,dx ;16余
mov si,ax ;低16商
mov ax,di ;左边
mov dx,0
div cx
mov dx,ax ;高16商
mov ax,si ;低16商
mov cx,bx ;16余
pop si
pop di
pop bx
ret
dtoc: ;函数
mov bx,ax ;备份ax
mov cx,10;准备条件
mov dx,0
mov si,1 ;初始一个数
dtoc1:
call divdw
mov cx,dx
or cx,ax
jcxz dtoc2
inc si
mov cx,10
jmp dtoc1
dtoc2: inc si
mov byte ptr [si],0 ;假设一个五个数,那下标最大到4,所以下标5的地方设置一个 \0
mov cx,si
sub si,1
mov ax,bx
mov dx,0
sub cx,1
sub si,1
dtoc3:
push cx
mov cx,10
call divdw
mov [si],cl
add byte ptr [si],'0' ; 填充字符
sub si,1
pop cx
loop dtoc3
ret
code ends
end start
运行结果:
课程设计1
更改dtoc
assume cs:code,ds:data
data segment
db 10 dup(0)
data ends
stack segment
db 128 dup(0)
stack ends
code segment
start:
mov ax,stack
mov ss,ax
mov sp,16
mov dx,00ffh
mov ax,65535;12666 将值换成16,777,215
mov bx,data
mov ds,bx
mov si,0
call dtoc
mov dh,8
mov dl,3
mov cl,2
mov di,0
mov si,0
call show_str
mov ax,4c00h
int 21h
show_str: ;函数
mov ax,0b800h
mov es,ax ;要显示的起始段 es
mov al,160
mul dh
mov dh,0
add dx,dx
add ax,dx
mov di,ax ;要显示的起始位置 di
mov ah,cl
s2: mov ch,0
mov cl,[si]
jcxz s3
mov ch,ah; 字符所在寄存器 cx
mov es:[di],cx
add di,2
inc si
jmp s2
s3: ret
divdw: ;dx,ax,cx divdw(dx,ax,cx) 商h商l 余数
push bx
push di
push si
mov di,dx
mov si,ax
;X/N = int(H/N)*65536+[rem(H/N)*65536+L]/N
;=左边+右边
mov ax,dx ;右边
mov dx,0h
div cx
mov ax,si
div cx ;dx,ax
mov bx,dx ;16余
mov si,ax ;低16商
mov ax,di ;左边
mov dx,0
div cx
mov dx,ax ;高16商
mov ax,si ;低16商
mov cx,bx ;16余
pop si
pop di
pop bx
ret
dtoc: ;函数
push di ;提前压入栈中
mov di,dx ;更改 将dx存放在di中
mov bx,ax
mov cx,10
mov si,1
dtoc1:
call divdw
mov cx,dx
or cx,ax
jcxz dtoc2
inc si
mov cx,10
jmp dtoc1
dtoc2: inc si
mov byte ptr [si],0
mov cx,si
sub si,1
mov dx,di ;弹出di
mov ax,bx ;弹出ax
sub cx,1
sub si,1
dtoc3:
push cx
mov cx,10
call divdw
mov [si],cl
add byte ptr [si],'0'
sub si,1
pop cx
loop dtoc3
pop di ;最后不忘弹出di
ret
code ends
end start
运行结果:
任务,将实验7中的Power idea公司的数据按照图10.2所示的格式在屏幕上显示出来。
修改后的显示ax中的数字:
assume cs:code,ds:data
data segment
db '1234',10 dup(0)
data ends
stack segment
db 128 dup(0)
stack ends
code segment
start:
mov ax,stack
mov ss,ax
mov sp,16
mov dx,00ffh
mov ax,65535;12666 将值换成16,777,215
mov bx,data
mov ds,bx
mov si,3
call dtoc
mov dh,8
mov dl,3
mov cl,2
mov di,0
mov si,0
call show_str
mov ax,4c00h
int 21h
show_str: ;函数 (dh,dl,cl,ds:si)
push ax
push bx
push di
mov ax,0b800h
mov es,ax ;要显示的起始段 es
mov al,160; 一行160字节
mul dh ;乘以dh行
mov dh,0
add dx,dx ;加上该行第几个
add ax,dx
mov di,ax ;要显示的起始位置 di
mov ah,cl ;字符属性
s2: mov ch,0
mov cl,[si] ;将字符放在该位置,结尾0结束
jcxz s3
mov ch,ah; 字符所在寄存器 cx
mov es:[di],cx
add di,2
inc si
jmp s2
s3: pop di
pop bx
pop ax
ret
divdw: ;dx,ax,cx divdw(dx,ax,cx) 商h商l 余数
push bx
push di
push si
mov di,dx
mov si,ax
;X/N = int(H/N)*65536+[rem(H/N)*65536+L]/N
;=左边+右边
mov ax,dx ;右边
mov dx,0h
div cx
mov ax,si
div cx ;dx,ax
mov bx,dx ;16余
mov si,ax ;低16商
mov ax,di ;左边
mov dx,0
div cx
mov dx,ax ;高16商
mov ax,si ;低16商
mov cx,bx ;16余
pop si
pop di
pop bx
ret
dtoc: ;函数 dtoc(dx,ax,ds:si)
push bx
push cx
push di ;提前压入栈中
push dx ;更改 将dx存放在di中
push ax
mov cx,10
mov di,1
dtoc1:
call divdw
mov cx,dx
or cx,ax
jcxz dtoc2
inc di
mov cx,10
jmp dtoc1
dtoc2:
add si,di
mov byte ptr [si],0
mov cx,di
sub si,1
pop ax ;弹出di
pop dx ;弹出ax
;sub cx,1
;sub si,1
dtoc3:
push cx
mov cx,10
call divdw
mov [si],cl
add byte ptr [si],'0'
sub si,1
pop cx
loop dtoc3
pop di ;最后不忘弹出di
pop cx
pop bx
ret
code ends
end start