ret 和 call 指令都是转移指令,它们都修改 IP,或同时修改 CS 和 IP,经常用它们来共同完成子程序的设计
ret 和 retf 指令
ret 指令用栈中的内容来修改 IP,实现近转移。
retf 指令用栈中的数据来修改 CS 和 IP 内容来实现远转移。
CPU 执行 ret 指令时相当于
- pop ip
CPU 执行 retf 指令时相当于
- pop ip
- pop cs
下面的程序 ret 指令执行后,CS:IP 执行代码段的第一条指令
assume cs:codesg,ss:stack |
在 debug 中可以清楚的看到当执行完 ret 指令后,CS:IP 指向了程序的第一条指令。
下面的程序 retf 指令执行后,CS:IP 执行代码段的第一条指令
assume cs:codesg,ss:stack |
在 debug 中可以清楚的看到当执行完 retf 指令后,CS:IP 指向了程序的第一条指令。
call 指令
执行 call 指令时,进行两步操作:
- 将当前的 IP 或 CS 和 IP 压入栈中
- 转移
call 指令不能实现短转移。
依据位移进行转移的 call 指令
call 标号(将当前的 IP 压入栈后再跳到标号处执行)
执行 “call 标号” 相当于一下两步操作
- push IP
- jmp near ptr 标号
转移的目的地址在指令中的 call 指令
指令 “call far ptr 标号” 实现的段间转移,CPU 在执行时相当于以下 3 步操作:
- push CS
- push IP
- jmp far ptr 标号
转移地址在寄存器中的 call 指令
指令 “call 16位寄存器” 被 CPU 执行时,相当于以下 2 步操作:
- push IP
- jmp 16位寄存器
转移地址在内存中的 call 指令
转移地址在内存中的 call 指令有两种格式
- call word ptr 内存单元地址
CPU 在执行上述指令的时候相当于以下 2 步:
- push IP
- jmp word ptr 内存单元地址
比如下面的代码:
assume cs:codesg |
在执行完 “call word ptr 内存单元” 后相当于执行了 “push ip” 和 “jmp word ptr 内存单元地址” 两条指令,push 后栈顶要向上移动 2 个单元,即 sp 中的值减 2(10H - 2 = 0EH)。“jmp word ptr 内存单元地址” 相当于用内存单元中的值去修改 IP,执行后 IP 为 0123H(“jmp word ptr 内存单元地址” 忘了的话返回前面的文章复习一下)。在 debug 中可以清楚的看到结果。

- call dword ptr 内存单元地址
CPU 在执行上述指令的时候相当于以下 2 步操作:
- push cs
- push ip
- jmp dword ptr 内存单元地址
例如下面的代码
assume cs:codesg |
执行 “call dword ptr ds:[0]” 后,cs 的值为 0 ,ip 为 0123H,sp 为 0ch,在 debug 中显示如下:

call 和 ret 的配合使用
我们知道使用 call 指令的时候会先进行入栈操作,然后再跳到标号处去执行程序,ret 是用栈中的数据去修改 IP 或 CS 和 IP 来达到控制 CPU 执行指令的目的,这样就可以用 call 和 ret 指令来实现调用子程序的目的。例如下面的程序是调用子程序来实现 2 的 3 次方并将结果存放到 bx 中:
assume cs:codesg |
执行完后,我们看到 bx 中内容为 8 ,是我们希望得到的结果,在 debug 中显示如下:

一些思考
上面的程序中我们没有向子程序中传递参数,那怎么实现向子程序传递参数呢?我们可以用一个寄存器来传递,即调用者将要传递的参数放到某个合法的寄存器中,子程序执行的时候来修改这个寄存器,这样就实现了传递参数并返回的功能。那么要传递一堆参数给子程序该怎么办呢?其实可以把这堆参数放到内存里,寄存器只保留这个内存的首部地址即可。要是调用者和子程序都使用了相同的寄存器,这岂不是混乱了?一般的做法是在调用子程序的时候把寄存器的值都入栈,等到子程序返回时再把出栈来恢复主程序寄存器的值。