汇编语言第十章call 和ret 指令
call 和 ret 指令都是转移指令它们都修改IP或同时修改cs和ip 。它们经常被共同用来实现子程序的设计这一章我们讲解call和ret指令的原理。10.1 ret 和retfcpu 执行ret指令时进行下面两步操作1(IP)((SS)*16(SP))2) ( SP)(SP)2CPU执行retf指令时进行下面四步操作1ip((ss)*16(sp))2) (sp) (sp)23) (cs)((ss)*16(sp))4) (sp)(sp)2可以看出如果我们用汇编语法来解释ret和retf指令则cpu 执行ret指令时相当于进行:pop ipcpu 执行retf指令时相当于进行pop ippop cs例下面的程序中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 startassume cs:code 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 cs push ax mov bx,0 retf code ends end start检测点 10.1补全程序实现从内存10000000处开始执行指令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,0 push ax mov ax,1000 push ax rets code ends end startrets相当于 ip(sp)cs(sp-2)10.2 call 指令1将当前的IP或CS和IP压入栈中2转移。103 依据位移进行转移的call 指令假设CPU正在执行call 标号 这条指令它其实干了两件小事。1保存回家路线。2传送过去修改指令指针强制跳转到标号所在的地址去执行。这里的“当前的IP”在 CPU 内部指的是call指令执行完毕之后下一条指令的地址也就是call指令的长度 call指令的起始地址下面的程序执行后AX中的数值为多少内存地址 机器码 汇编指令10000 b8 00 00 mov ax,01000:3 e8 01 00 call s1000: 6 40 inc ax1000:7 58 s:pop axax 中的数值为6. call s 表示把它的下一条指令地址入栈。也就是 6入栈。然后跳转到10007 执行。10.4 转移的目的地址在指令中的call指令前面讲的call 指令其对应的机器指令中没有转移的目的地址而是相对于当前ip的转移位移。指令call far ptr 标号 实现的是段间转移。cpu执行此种格式的call 指令时进行如下的操作1 sp sp-2((ss)*16(sp))(cs)(sp)(sp)-2((ss)*16(sp))(ip)2) (cs)标号所在段的段地址ip 标号在段中的偏移地址call far ptr 标号时相当于进行push cspush ipjmp far ptr 标号10.6 转移地址在内存中的call指令110.7 Call 和Ret 的配合使用前面我们已经分别学习了ret 和 call 指令的原理。 现在我们来看一下如何讲他们配合使用来实现 子程序的机制。问题10.1 下面程序返回前bx中的值是多少assume cs:code code segment start: mov ax,1 ;ax1 mov cx,3 ;cx3 call s mov bx,ax ;(bx)?,所以bx8 mov ax,4c00h int 21h s: add ax,ax ;ax2,ax4,ax8 loop s ;cx2,cx1,cx0 ret ;返回 code ends end startassume cs:code stack segment db 8 dup (0) ; 1000:0000 00 00 00 00 00 00 00 00 db 8 dup (0) ; 1000:0008 00 00 00 00 00 00 00 00 stack ends code segment start: mov ax,stack ; 1001:0000 B8 00 10 mov ss,ax ; 1001:0003 8E 00 mov sp,16 ; 1001:0005 BC 10 00 mov ax,1000 ; 1001:0008 B8 EB 03 call s ; 1001:000B E8 05 00 mov ax,4c00h ; 1001:000E B8 00 4c int 21h ; 1001:0011 CD 21 s: add ax,ax ; 1001:0013 03 C0 ret ; 1001:0015 C3 code ends end startcall ret 相当于函数调用。10.8 mul 指令1) 两个相乘的数两个相乘的数要么都是8位要么都是16位。如果是8位一个默认放在AX中另一个放在8位寄存器或内存字节单元中如果是16位一个默认在AX中另一个放在16位寄存器或内存字单元中。2结果如果是8位乘法结果默认放在AX中 如果是16位乘法结果高位默认在DX中存放低位在AX中存放。格式如下mul regmul 内存单元内存单元可以用不同的寻址方式给出比如mul byte ptr ds:[0]含义为(ax)(al)*((ds)*160)mul word ptr[bxsi8]1计算100*102计算100*10000100小于255,可10000大于255所以必须做16位乘法程序如下10.9 模块化设计从上面我们看到call 和ret 指令共同支持了汇编语言编程中的模块化设计。在实际编程中程序的模块化是必不可少的。10.10 参数和结果传递的问题例如我们设计一个子程序可以根据提供的N来计算N的三次方。1我们将参数N 存储在什么地方2计算得到的数值我们存储在什么地方很显然我们可以用寄存器来存储可以将参数放到bx中因为子程序中要计算NXNXN,可以使用多个mul 指令为了方便可以将结果放到dx和ax中。10.11 批量数据的传递一个参数可以放到寄存器。多个参数可以放到内存中。10.12 寄存器冲突的问题子程序中使用的寄存器主程序中也可能使用造成了寄存器使用上的冲突。那么如何避免这种冲突呢有两种方案1在编写调用子程序的程序时注意看看子程序中有没有用到会产生冲突的寄存器如果有调用者使用别的寄存器。2在编写子程序的时候不要使用会产生冲突的寄存器。事实上上面很难实现。 其实我们希望1编写调用子程序的程序的时候不必关心子程序到底使用了哪些寄存器。2编写子程序的时候不必关心调用者使用了那些寄存器。3不会发生寄存器冲突。实验10 编写子程序1、显示字符串2、解决除法溢出的问题3、数值显示问题