4. 用call与ret实现汇编的模块化编程

call 和 ret 都是转移指令,他们可以共同使用实现汇编中的中的子程序设计

call 指令

call 近转移

使用格式:call label

当执行call指令时,CPU可以认为执行2个操作:

  1. push ip + 3 (将call后的下一条指令的ip压入栈中)
  2. 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 可以认为是:

  1. pop ip
  2. 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

还有要注意设计子程序时,要注意寄存器冲突(子程序修改了主程序要用的寄存器),解决方案一个时避免使用冲突的寄存器,另一个则是提前将寄存器的值压入栈中,子程序返回前再从栈中弹出

《4. 用call与ret实现汇编的模块化编程》上有3条评论

发表评论

邮箱地址不会被公开。