La instrucción ret extrae una palabra de la pila y la coloca en ip, produciendo un retorno dentro del mismo segmento de código. Internamente equivale a pop ip.
assume cs:codigo, ss:pila
pila segment
dw 20 dup (0)
pila ends
codigo segment
inicio:
mov ax, pila
mov ss, ax
mov sp, 40
mov ax, offset continuar
push ax
ret
continuar:
mov ax, 4c00h
int 21h
codigo ends
end inicio
retf: retorno lejano
Para regresar de una subrutina intersegmento se emplea retf, que restaura primero ip y luego cs desde la pila. Equivale a pop ip seguido de pop cs.
assume cs:codigo, ss:pila
pila segment
dw 20 dup (0)
pila ends
codigo segment
principal:
mov ax, pila
mov ss, ax
mov sp, 40
push cs
mov ax, offset siguiente
push ax
retf
siguiente:
mov ax, 4c00h
int 21h
codigo ends
end principal
call: llamada a subrutina
call almacena en la pila la dirección de retorno y transfiere el control al destino. En su forma cercana guarda solo ip; en la lejana guarda cs:ip. A diferencia de jmp, no admite salto corto.
assume cs:codigo
codigo segment
principal:
mov ax, 2
call duplicar
; ax ahora vale 4
mov ax, 4c00h
int 21h
duplicar:
add ax, ax
ret
codigo ends
end principal
También es posible llamar a través de un registro o de una dirección de memoria:
lea bx, duplicar
call bx
; o bien
call word ptr [tabla_funciones]
call far ptr y retf
Una llamada lejana con call far ptr empuja cs e ip. La subrutina correspondiente debe finalizar con retf para restaurar ambos registros.
call far ptr tarea_lejana
...
tarea_lejana:
mov bx, ax
retf
call y ret para implementar subrutinas
El par call/ret permite crear procedimientos reutilizables. El siguiente ejemplo calcula una potencia entera medainte multiplicaciones sucesivas.
assume cs:codigo
codigo segment
principal:
mov ax, 1 ; resultado inicial
mov cx, 4 ; exponente
call potencia
mov ax, 4c00h
int 21h
potencia:
mov bx, 3 ; base
multiplicar:
mul bx
loop multiplicar
ret
codigo ends
end principal
Multiplicación con mul
mul multiplica operandos sin signo. Si los operandos son de 8 bits, el resultado se aloja en ax; si son de 16 bits, el resultado de 32 bits se guarda en dx:ax.
; 8 bits
mov al, 50
mov bl, 20
mul bl ; ax = 1000
; 16 bits
mov ax, 2500
mov bx, 400
mul bx ; dx:ax = 1 000 000
Diseño modular
Dividir un programa en subrutinas pequeñas mejora la organización y la reutilización. Cada rutina debe realizar una única tarea clara y documentarse con comentarios que indiquen los registros de entrada y el valor devuelto.
Paso de parámetros y resultados
Los registros son el medio más directo. El siguiente procedimiento recibe dos sumendos en ax y bx y deja el resultado en ax.
sumar:
add ax, bx
ret
; uso
mov ax, 7
mov bx, 13
call sumar ; ax = 20
Transferencia de bloques de datos
Para procesar muchos elementos se pasa un puntero al inicio del bloque y un contador. El siguiente ejemplo convierte una cadena a mayúsculas.
assume cs:codigo, ds:datos
datos segment
mensaje db 'EjemploXyZ'
datos ends
codigo segment
principal:
mov ax, datos
mov ds, ax
mov si, offset mensaje
mov cx, 10
call mayusculas
mov ax, 4c00h
int 21h
mayusculas:
and byte ptr [si], 11011111b
inc si
loop mayusculas
ret
codigo ends
end principal
Como alternativa general, los parámetros también pueden apilarse antes de llamar a la subrutina, aunque entonces la rutina debe conocer el convenio de pila acordado.