Вы находитесь на странице: 1из 5

Apuntes de Lenguaje Ensamblador Luis A.

Zarza López, UTM, 2017

12. Uso avanzado de la pila


La pila es la utilización del segmento de pila para hacer operaciones de pila con datos. Se utilizan,
principalmente, las instrucciones PUSH y POP. Se utiliza el registro apuntador SP para indicar cuál es
el tope de la pila.

PUSH: poner dato de 16 bits en la pila


PUSH AX
PUSH dato
PUSH [BX]

El PUSH AX equivale a:
SUB SP,02
MOV [SP],AX

POP: copiar (extraer) 16 bits de la pila


POP AX

El POP AX equivale a:
MOV AX,[SP]
ADD SP,02

Nota: hay veces en que se desea liberar n posiciones de la pila, sin alterar ningún registro, por lo que se
le agrega al apuntador de pila SP dos posiciones por cada 16 bits. Si se hizo un PUSH (16 bits) se debe
hacer:
ADD SP,02

Como ya se mencionó con anterioridad, la pila se utiliza automáticamente cuando se invoca una
función. Es por ello que no se debe intentar almacenar un dato en la pila con un PUSH antes de invocar
una función, y reclamar el dato dentro de la función con el respectivo POP, pues estando dentro de la
función, el dato disponible en la pila viene siendo la dirección de retorno, en lugar del dato deseado. La
excepción a advertencia, viene siendo el paso de parámetros a una función, que utiliza una técnica muy
ingeniosa con el uso de la memoria, y que será explicada a continuación.

Uso de la pila con funciones


Ya se ha dicho que para llamar a funciones, se utiliza CALL y termina con RET. Estas instrucciones
hacen uso de la pila para almacenar y recuperar la dirección de retorno. Sin embargo, las funciones
también usan la pila para el paso avanzado de parámetros y para variables locales.

32
Apuntes de Lenguaje Ensamblador Luis A. Zarza López, UTM, 2017

Hasta ahora, para pasar un parámetro, se ha utilizado DX y algún otro registro ocasional. Pero esto
limita el número y tamaño de parámetros que se pueden pasar.
Para paso de múltiple de parámetros, por convención, se utiliza la pila, de la siguiente manera:
mov ax,03
push ax ;pila: 0003
mov ax,05
push ax ;pila: 0005 0003
call funcion
;pila: 0005 0003 (después de volver)
add sp,04 ;pila: (vacía)

Se puede ver que los dos parámetros se ponen en AX (puede ser cualquier registro) y se introducen en
la pila, y se invoca la función. Al salir, podemos suponer que la pila sigue ocupada por esos datos. Para
restaurar la pila sin copiar los datos en ninguna parte (no se les requiere), se le agrega 2 al SP por cada
16 bits introducidos a la pila. En este caso, puesto que fueron dos PUSH de 16 bits, se suma 4 al
apuntador SP, y la pila quedará vacía (en lo que respecta a nuestro código, pues la pila podría traer
datos introducidos antes).
La función invocada debe incluir código adicional para poder acceder a los parámetros y para poder
usar variables locales. Se hará uso de un apuntador especial llamado base pointer (BP). Dentro de una
función, se debe respaldar el valor que traía (usado por alguna otra función previa) y crear un nuevo
valor (dirección) que servirá como base para acceder a los parámetros y variables locales. También se
restarán n posiciones al SP para disponer de n bytes para variables locales. Al bajar el apuntador del SP,
esos espacios en la pila no serán utilizados por los posteriores PUSH y POP hechos dentro de la
función. La función quedará así:
fun: ;pila: retH retL 0005 0003 (al entrar)

push bp ;Se respalda el antiguo valor de BP


;pila: BPH BPL retH retL 0005 0003

mov bp,sp ;Se crea el nuevo valor de BP


; el cual apunta al anterior BP

sub sp,4 ;Reserva 4 bytes para var locales


;pila: var2H var2L var1H var1L BPH BPL retH retL 0005 0003

;A partir de aquí se desarrolla la función.


;Último parámetro (0005) disponible en [BP+4] (observe la pila).
;Penúltimo parámetro (0003) en [BP+6]. De haber más, estarían
; en [BP+8], [BP+10], etc.
;Primera variable local de 16 bits disponible en [BP-2] para
; lectura y escritura.
;Segunda variable local disponible en [BP-4]
salida:

32
Apuntes de Lenguaje Ensamblador Luis A. Zarza López, UTM, 2017

;El resultado deberá estar en AX.


mov sp,bp ;Restaurar SP para recuperar espacio
; de variables locales
; y posibles errores.
;pila: BPH BPL retH retL 0005 0003
pop bp ;Se restaura el antiguo valor de BP.
;retH retL 0005 0003
ret ;pila: 0005 0003
;Los dos últimos espacios se recuperan
; en el código que invocó la función.

Como puede observarse, para el acceso a parámetros y variables locales se utiliza el apuntador BP, el
cual se encuentra definido dentro de la función. Sin importar el tamaño de la pila, usando posiciones a
partir de BP, se pueden acceder esas posiciones en la pila. Este uso de la pila para parámetros y
variables locales es muy ingenioso porque la asignación de esos espacios es dinámica y se libera de
manera muy simple al salir de las funciones. Esta técnica es utilizada por casi todos los lenguajes de
programación estructurada y orientada a objetos (si no, es que todos) para el paso de parámetros y
variables locales. Es muy importante saber cómo se reserva este espacio, para no esperar que los datos
ahí almacenados aún estén disponibles después de salir de la función. Devolver el apuntador a una
variable local para acceder al dato desde afuera es una práctica terrible.

Funciones recursivas con pila


Utilizar la pila para pasar parámetros que siempre estarán disponibles para la función sin depender de
registros permite utilizar funciones recursivas, ya que cada invocación de una función siempre tendrá
su propio espacio de parámetros y variables locales, aunque la función llame a la misma función
(mismo código) de manera recursiva.
La expresión del algoritmo de una sumatoria recursiva se puede expresar de la siguiente manera:

Algoritmo SumatoriaRecursiva( n )
1. Si n es 1, entonces
1.1 Devolver 1 (salir)
2. Devolver n + SumatoriaRecursiva( n-1 )

En el paso 2, se invoca el mismo algoritmo, pero pasando el valor de n menos 1.


Si el algoritmo se invoca con 1, devolverá 1 (ejecutará sólo el paso 1).
Si el algoritmo se invoca con 2, devolverá
2 + SumatoriaRecursiva(1) =
2+1=
3
Si el algoritmo se invoca con 3, devolverá
3 + SumatoriaRecursiva(2) =

32
Apuntes de Lenguaje Ensamblador Luis A. Zarza López, UTM, 2017

3 + (2 + SumatoriaRecursiva( 1 ) ) =
3 + (2 + 1) =
6

Es importante darse cuenta de que muchas personas no alcanzan a entender la recursividad, su alcance
y aplicaciones. Conviene revisar la explicación anterior varias veces para estar seguro de que se puede
entender.
A continuación se muestra el código del programa que calcula la sumatoria recursiva.

;Sumatoria recursiva
main: call lee2 ;dato en AL
call reto
push ax ;Parámetro en la pila.
;Dato de 8 bits, se ignorará parte alta
call sumat ;Resultado en AL .
add sp,2 ;Restauración de pila
mov dl,al
call des2 ;Despliegue de resultado.
.exit 0

sumat: push bp
mov bp,sp ;Se crea nuevo BP.
;Detectar condición de salida
mov ax,[bp+4]
cmp al,1 ;Si parámetro es 1
je salida ; proceder a la salida
; ya teniendo 1 en AL.
;Calcular resultado para actual invocación
mov ax,[bp+4] ;Obtener parámetro, ignorar parte alta.
dec al ; decrementarle 1
push ax ; y prepararlo como parámetro.
call sumat ;Se invoca la misma función,
; tendrá su propio espacio.
;Resultado de sumat(n-1) en AL.
add sp,2 ;Se restaura la pila.
mov bx,[bp+4] ;Se carga parámetro para operación,
add al,bl ; y se efectúa la suma, que será:
; n + sumat(n-1)

salida:;ya tengo en AL el resultado


mov sp,bp
pop bp
ret

32
Apuntes de Lenguaje Ensamblador Luis A. Zarza López, UTM, 2017

Ejercicios:
1. Modificar el ejemplo de sumatoria para que efectúe el factorial de un número. No exceder de 16 bits
en el resultado.
2. Hacer un programa que calcule el valor de fibonacci(x), donde el algoritmo viene dado por:

Algoritmo FibonacciRecursivo( n )
1. Si n es menor o igual a 2, entonces
1.1 Devolver 1 (salir)
2. Devolver FibonacciRecursivo( n-1 ) + FibonacciRecursivo( n-2 )

3. Hacer un programa que determine los movimientos para resolver el problema de Las Torres de Hanoi
mediante una función recursiva. Este problema es particularmente difícil de entender pero muy fácil de
implementar. Se deberá investigar con cuidado las características del problema y sus reglas, y probarlo
manualmente utilizando monedas de tamaños diferentes, para torres de una moneda, dos monedas, tres
monedas y hasta cuatro monedas.
Si ya se entiende el problema se puede proseguir con la explicación siguiente:
El algoritmo de Hanoi recursivo inicia considerando el problema de mover una pila de discos como
sólo mover dos partes: 1) n-1 discos (como una sola cosa), y 2) el disco de abajo, utilizando tres
movimientos (como resolver el problema con 2 monedas). Mover n-1 discos tal cual es una violación a
las reglas, pero esta operación se resuelve invocando recursivamente el algoritmo, pero ahora con un
disco menos. Cada invocación al algoritmo indica de dónde a dónde hay que mover, cuál es el poste
auxiliar, y cuántos discos tiene el problema).

Algoritmo HanoiRecursivo(poste_origen,poste_auxiliar,poste_destino,n)
1. Si n es 0, salir (no hay nada que mover).
2. HanoiRecursivo(poste_origen,poste_destino,poste_auxiliar, n-1 )
3. Mover disco de poste_origen a poste_destino (indicar movimiento)
4. HanoiRecursivo(poste_auxiliar,poste_origen,poste_destino, n-1 )

Es importante notar que en los pasos 2 y 4 se están intercambiando los postes, pues para los
movimientos internos de discos, se intercambian los roles de los postes. En el paso 2, se utiliza el poste
destino como auxiliar, y el auxiliar como destino. También se hacen intercambios en el paso 4. En el
paso 3, el programa deberá desplegar en pantalla el movimiento a realizar, del origen al destino (de
acuerdo a los parámetros recibidos en esa invocación específica). Como parámetros, se pueden pasar
los valores ASCII de las letras A, B y C.

32

Вам также может понравиться