汇编第十节-CALL和RET指令

第十节:CALL和RET指令

retf和retf经常被用来实现子程序的设计

1.ret和retf:

ret指令用栈中的数据,修改IP的内容,从而实现近转移
CPU执行ret指令时,进行下面两步操作:

a.(IP)=((ss)*16+(sp))
b.(sp)=(sp)+2

汇编语言相当于进行 :pop IP
ret指令执行后,(IP)=0,CS:IP指向代码段的第一条指令

assume cs:code
stack segment
    db 16 dup(0)
stack ends
code segment
      mov ax,4c00h
      int 21h   
start:mov ax,stack
      mov ss,ax
      mov sp,16
      mov ax,0
      push ax
      mov bx,0
      ret 
code ends
end start

retf指令用栈中的数据,修改CS和IP的内容,从而实现远转移
CPU执行retf指令时,进行下面四步操作:

a.(IP)=((ss)*16+(sp))
b.(sp)=(sp)+2
c.(CS)=((ss)*16+(sp))
d.(sp)=(sp)+2

汇编语言相当于进行:pop IP,pop cs
retf指令执行后,CS:IP指向代码段的第一条指令

assume cs:code
stack segment
    db 16 dup(0)
stack ends
code segment
      mov ax,4c00h
      int 21h   
start:mov ax,stack
      mov ss,ax
      mov sp,16
      mov ax,0
      push ax
      mov bx,0
      retf
code ends
end start

2.call指令:

CPU执行call指令时,进行两步操作:
a.将当前的IP或CS和IP压入栈中;
b.转移

call指令不能实现短转移,除此之外,call指令实现转移的方法和jmp指令的原理相同

3.依据位移进行转移的call指令:

call 标号(将当前的IP压栈后,转到标号处执行指令)
CPU执行此格式call指令时,进行如下操作:

a.(sp)=(sp)-2
b.((ss)*16+(sp))=(IP)
c.(IP)=(IP)+16位位移

16位位移=标号处的地址-call指令后的第一个字节的地址
16位位移的范围为-32768~32767,用补码表示
16位位移有编译程序在编译时算出

汇编语言相当于进行

push IP
jmp near ptr 标号

4.转移的目的地址在指令中call指令:

前面的call指令都是相对于当前IP的转移位移
call far ptr 标号 ;实现的段间转移

a.(sp)=(sp)-2
b.((ss)*16+(sp))=(CS)
c.(sp)=(sp)-2
d.((ss)*16+(sp))=(IP)

(CS)=标号所在段的段地址
(IP)=标号在段中的偏移地址
CPU执行call far ptr 标号 相当于进行:

push CS
push IP
jmp far ptr 标号

5.转移地址在寄存器中的call指令:

call 16位 reg
功能:

(sp)=(sp)-2
((ss)*16+(sp))=(IP)
(IP)=(16位reg)

CPU执行call 16位 reg时,相当于进行

push IP
jmp 16位 reg 

6.转移地址在内存中的call指令:

a.call word ptr 内存单元地址
执行相当于:push IP /jmp word ptr 内存单元地址
b.call dword ptr 内存单元地址
执行相当于:push CS /push IP /jmp dword ptr 内存单元地址

7.call和ret的配合使用:

assume cs:code
code segment 
  start:mov ax,1
        mov cx,3
        call s
        mov bx,ax
        mov ax,4c00h
        int 21h
    s:  add ax,ax
        loop s
        ret
code ends
end start

执行过程,cpu将call s指令的机器码读入,IP指向了call s 后的指令 mov bx,ax,然后cpu执行call s指令,将当前的IP值(指令 mov bx,ax的偏移地址)压栈,并将IP的值改变为标号S处的偏移地址,cpu从标号s处开始执行指令,loop循环完毕,(ax)=8;cpu将ret指令的机器码读入,IP指向了ret指令后的内存单元,然后cpu执行ret指令,从栈中弹出一个值(即call s 先前压入的mov bx,ax 指令的偏移地址)送入IP中,则CS:IP指向指令mov bx,ax;CPU从mov bx,ax开始执行指令,直至完成。
故 (bx)=8
从标号s到ret的程序段的作用是计算2的N次方。N的值由cx提供
从上面的讨论中可以发现,可以写一个具有一定功能的程序段,称其为子程序,在需要的时候,用call指令转去执行。
子程序的框架如下:
标号:
指令
ret

assume cs:code
code segment
  main::
       :
       call sub1        ;调用子程序sub1
       :
       :
       mov ax,4c00h
       int 21h

  sub1::                ;子程序sub1开始
       :
       call sub2        ;调用子程序sub2
       :
       :
       ret              ;子程序返回

  sub2::                ;子程序sub2开始
       :
       :
       ret              ;子程序返回
code ends
end main    

8.mul指令:

mul是乘法指令
a.两个相乘的数,要么都是8位,要么都是16位。
如果是8位,一个默认在AL中,另一个放在8位reg或内存字节单元中;
如果是16位,一个默认在AX中,另一个放在16位reg或内存字单元中;
b.结果:如果是8位乘法,结果默认放在AX中;
如果是16位乘法,结果高位默认在DX中存放,低位在AX中存放。
格式如下:
mul reg
mul 内存单元
内存单元可以用不同的寻址方式给出:比如:
mul byte ptr ds:[0] /含义 (ax)=(al)*((ds)*16+0)

mul word ptr [bx+si+8]
/含义:
(ax)=(ax)*((ds)*16+(bx)+(si)+8)结果的低16位
(dx)=(ax)*((ds)*16+(bx)+(si)+8)结果的高16位
eg:计算100*10000
100<255;可10000>255 必须进行16位乘法

mov ax,100
mov bx,10000
mul bx

结果(ax)=4240H,(dx)=000FH (F4240H=1000000)

9.模块化程序设计:

call和ret 指令共同支持了汇编语言编程中模块化设计,可以用简捷的方法,实现多个相互联系,功能独立的子程序来解决一个复杂的问题

10.参数和结果传递的问题:

子程序一般都要根据提供的参数处理一定的事务,处理后,将结果(返回值)提供给调用者。其实,讨论参数和返回值传递的问题,实际上就是在探讨,如何存储子程序需要的参数和产生的返回值。
用寄存器来存储参数和结果是最常使用的方法,对于存放参数的寄存器和存放结果的寄存器,调用者和子程序的读写操作恰恰相反:调用者将参数送入参数寄存器,从结果寄存器中取得返回值;子程序从参数寄存器中取得参数,将返回值送入结果寄存器。
eg:计算data段中第一组数据的3次方,结果保存在后面一组dword单元中。

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        ;ds:si指向第一组word单元
        mov di,16       ;ds:di指向第二组dword单元

        mov cx,8
    s : mov bx,[si]
        call cube
        mov [di],ax
        mov [di].2,dx
        add si,2        ;ds:si指向下一个word单元
        add di,4        ;ds:di指向下一个dword单元
        loop s

        mov ax,4c00h
        mov int 21h
  cube:mov ax,bx
        mul bx
        mul bx
        ret
code ends
end start

11.批量数据的传递:

上例cube只有一个参数,放在bx中,如果由多个参数,该如何存放?
这个将批量数据放在内存中,然后将它们所在的内存空间的首地址放在寄存器中,传递给需要的子程。
eg:将一个全是字母的字符串转化为大写。

assume cs:code
data segment
    db 'conversation'
data ends
code segment
  start:mov ax,data
         mov ds,ax 
         mov si,0       ;ds:si指向字符串(批量数据)所在空间的首地址
         mov cx,12      ;cx存放字符串的长度
         call capital
         mov ax,4c00h
         int 21h
capital:and byte ptr [si],11011111b
         inc si
         loop capital
         ret
code ends
end start       

12.寄存器冲突的问题:

eg:将一个全是字母,以0结尾的字符串,转化为大写;
注意:在编写调用子程序的程序,注意看看子程序中有没有用到会产生冲突的寄存器。
在编写子程序的时候,不要使用会产生冲突的寄存器
*错误程序:

code segment 
assume cs:code
data segment
    db  'word',0
    db  'unix',0
    db  'wind',0
    db  'good',0
data ends
code segment
  start:mov ax,data 
         mov ds,ax
         mov bx,0
         mov cx,4
      s:mov si,bx
         call capital
         add bx,5
         loop s

         mov ax,4c00h
         int 21h

capital: mov cl,[si]
         mov ch,0
         jcxz ok
         and byte ptr [si],11011111b
         inc si
         jmp short capital
      ok:ret
code ends
end start

错误:问题在于cx的使用,主程序要使用cx记录循环次数,可是子程序中也使用了cx,在执行子程序的时候,cx中保存的循环计数值被改变,使得主程序的循环出错。
如何解决?
在子程序的开始将子程序中所有用到的寄存器中的内容都保存起来,在子程序返回前再恢复。可以用栈保存寄存器中的内容。

code segment 
assume cs:code
data segment
    db  'word',0
    db  'unix',0
    db  'wind',0
    db  'good',0
data ends
code segment
  start:mov ax,data 
         mov ds,ax
         mov bx,0
         mov cx,4
      s:mov si,bx
         call capital
         add bx,5
         loop s

         mov ax,4c00h
         int 21h
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
code ends
end start        

·

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值