call 和 ret 都是转移指令,他们可以共同使用实现汇编中的中的子程序设计
call 指令
call 近转移
使用格式:call label
当执行call指令时,CPU可以认为执行2个操作:
- push ip + 3 (将call后的下一条指令的ip压入栈中)
- jmp near ptr label (跳转到label所在的ip)
call 远转移
当要进行段间转移时,使用 call far ptr 指令(在emu8086中无效,可以用’转移地址在内存中的call’代替),该指令会将当前的cs与ip先后压入栈中,并进行等效于 jmp far ptr 的跳转
转移地址在寄存器中的call指令
格式:call reg,在把当前IP压入栈中后跳转到16reg中的地址
转移地址在内存中的call指令
分为两类,近转移与远转移
call word ptr [Address]
call dword ptr [Address]
mov word ptr [0], func ;偏移地址 mov word ptr [2], code ;段地址 call dword ptr [0]
ret 与 retf 指令
与call指令对应的,ret与retf指令从栈中获得跳转地址进行跳转。ret指令对应call的近转移,从栈中弹出一个地址给ip。retf对应远转移,从栈中弹出两个元素分别修改ip与cs
ret 指令还可以与立即数一起使用,如 ret N 可以认为是:
- pop ip
- add sp, N
这样可以实现用栈来转递参数
汇编的模块化编程
使用call与ret可以实现对子程序的调用与返回
如以下子程序实现了计算ax的立方:
; 描述:计算N的立方根 ; 参数:(ax) = N ; 结果:(bx ax) = N^3 cube: push cx ; 保存cx中原来的元素 mov cx, ax ; 用cx保存ax mul cx ; 计算N^2 mul cx ; 计算N^3 pop cx ; 恢复cx ret ; 返回
这里使用了寄存器来转递参数,更常用的作法是使用栈来传递参数,如同样的程序可以改写为:
; 描述:计算N的立方根 ; 参数: N = 栈中除IP外的第二个元素 ; 返回:(bax ax) = N^3 cube2: push bp ; 保存bp mov bp, sp ; 将bp指向栈顶 mov ax, ss:[bp+4] ; 从栈中获得参数 mov bp, ax ; 计算N^3 mul bp mul bp pop bp ; 恢复bp ret 2 ; 将栈顶指向参数前,并返回
调用时则:
push 2 call cube2
还可以用寄存器/栈转递地址,子程序从内存中获得数据(常用于批量数据的传递),以下程序使用这种方法在屏幕的指定位置打印内存中的数据:
; 描述:在屏幕的指定位置打印字符串 ; 参数:起始位置 <- 栈中第5个元素 ; 长度 <- 栈中第4个元素 ; Y位置 <- 栈中第3个元素 ; X位置 <- 栈中第2个元素 ; 结果:无 print: push ax ;中转 push bx ;字符串寻址 push cx ;控制循环 push si ;输出偏移地址 push es ;输出段地址 mov bx, sp mov ax, ss:[bx+14] ;获得输出的偏移位置 mov cl, 158 mul cl mov si, ax mov ax, ss:[bx+12] add si, ax add si, ax mov cx, ss:[bx+16] ;获得长度 mov bx, ss:[bx+18] ;获得字符串地址 mov ax, 0B800H mov es, ax ;获得输出的段地址 print_loop: mov al, [bx] ;获得字符 mov es:[si], al ;输出 add bx, 1 add si, 2 loop print_loop pop es pop si pop cx pop bx pop ax ret 8
还有要注意设计子程序时,要注意寄存器冲突(子程序修改了主程序要用的寄存器),解决方案一个时避免使用冲突的寄存器,另一个则是提前将寄存器的值压入栈中,子程序返回前再从栈中弹出
啦啦啦
评论都要审核?
原来不用审核呀