同学们好
我们这一节来学习CALL和RET指令
CALL和RET指令是属于子程序指令
CALL指令是用在主程序当中
用于调用子程序的
而RET指令是用在子程序最后
用于返回主程序的
当然返回的位置显然应该是
CALL调用指令下面的一条指令
那么实现这样的一个调用返回
实际上它是要通过一个堆栈的访问来实现的
也就是CALL指令
需要把我们的返回地址压入堆栈
这是它实现的一个功能
然后再实现跳转到目标位置
同样RET指令也可以认为有两个功能
一个功能是把我们的返回地址从堆栈弹出来
第二个功能就是转移到我们的返回地址
这中间要用到了堆栈
堆栈当中保存的就是CALL指令
下一条指令的地址
具体来说CALL指令我们有三种书写的方式
和我们的JMP指令类同
我们可以直接调用一个label
也就是子程序名字用label来表示调用子程序
也可以通过寄存器来指示这个子程序的位置
这是寄存器的间接寻址
也还可以通过存储器间接寻址访问到子程序
也就是子程序的这个开始位置 入口地址
放在了存储单元当中
我们通过调用这个存储单元
也可以实现调用子程序
和JMP指令也类同的
它也分成段内的和段间的调用
或者叫近调用 远调用
也支持所谓的相对寻址 直接寻址或间接寻址
返回指令也有两个形式
一个是带i16的参数 一个是不带
而带这个i16的参数
实际上是又增加了一步功能
把ESP这个堆栈的栈顶指针进行了增量
当然很多时候我们可能不需要
用对ESP增量的指令
我们就直接写RET return 返回
也就说ESP并没有改变
或者说仅仅是RET指令本身的改变
没有再附加上进行改变
CALL指令 RET指令
我们前面说了都分段内和段间
在我们的指令助记符当中并没有区别
但是在我们的汇编 连接过程当中
我们的汇编程序会正确的区别
只要我们编写的程序是正确的
那么我们再具体的来看一看
在段内的调用和返回指令的功能
我们把段间的 略微复杂的我们先放一放
先看一看段内调用和返回的（指令）
那么实际上段内的（调用和返回）
因为只涉及到对ESP的调整
那么段内的调用只涉及到更改偏移地址
这个时候我们只需要保存返回地址
当然是通过压入堆栈
那么压入堆栈
我们可以用一条push指令来实现
所以说入栈返回地址这一个功能
就可以用一条push指令来替代
当然压入的内容应该是CALL指令之后的
那一条指令的地址
那我们这里就假设了
是用了一个next标号来代替
那么第二个功能是实现跳转
那么jmp指令就是实现跳转
所以说我们完全可以用一条jmp指令
来替代CALL指令的第二个功能
换句话说CALL指令功能在这儿
就可以用一条push指令和一条jmp指令来替代
当然这样书写起来会繁琐
所以调用指令还是非常有必要设计的
那RET指令
就这样的一条指令就实现了
把堆栈的栈顶内容弹出来
直接弹到的是EIP
而EIP实际上就是下一条指令
next这个标号的这个地址
弹到EIP就是去它指示的这个位置去执行
所以说似乎它已经包含了有JMP指令的功能了
总之我们的CALL指令（调用）和RET返回指令
是实现了主程序调用和子程序的返回
其中要涉及到堆栈
也就是把我们的返回地址压入堆栈
不知道你理解了没有
你不妨看一看下面这个程序片段
第一条指令实现了对下一个标号处的调用
而下一条指令是把堆栈顶的内容弹出到EAX
这个时候EAX会是什么呢
回忆一下堆栈里头的内容
希望你能够得到答案
好 这一节讲到这儿