• No se han encontrado resultados

Parámetro de entrada. Un parámetro pasado del procedimiento que hace la llamada al procedimiento invocado.

N/A
N/A
Protected

Academic year: 2021

Share "Parámetro de entrada. Un parámetro pasado del procedimiento que hace la llamada al procedimiento invocado."

Copied!
154
0
0

Texto completo

(1)

CAPITULO 12 PROCEDIMIENTOS

En el capítulo 6, aprendiste como utilizar un programa con procedimientos, en este capítulo aprenderás a usar las directivas e instrucciones que definen y llamas procedimientos. Después de cubrir estos conceptos básicos, pasar información entre procedimientos y linkear partes separadas en un programa completo.

Nuevos Términos

Parámetros. Un campo dato que es mandado de un procedimiento a otro. Paso. Mandar un campo dato de un procedimiento a otro.

Parámetro de entrada. Un parámetro pasado del procedimiento que hace la llamada al procedimiento invocado.

Parámetro de salida. Un parámetro pasado del procedimiento que fue llamado de regreso al procedimiento que invoco.

Tabla de salto. Una lista de direcciones, cada una representa una locación a la cual el programa puede saltar.

12.1 Diseñar procedimientos que usan procedimientos.

Un buen diseñador de programas divide un programa en procedimientos. Por ejemplo, digamos que quieres escribir un programa que cuenta el número de líneas en un archivo. El programa puede consistir de un procedimiento principal que procese cada línea. Este proceso podría llamar uno o dos procedimientos, uno de lectura de líneas del archivo y uno que despliegue los resultados.

Aquí hay algunas reglas que puedes seguir para dividir un programa:

Diseña tus programas para que cada procedimiento ejecute solo una función. Haz procedimientos pequeños.

En lenguaje ensamblador, cada procedimiento está definido por las directivas PROC y ENDP. Un procedimiento es llamado por una instrucción CALL y regresa al programa que hizo la llamada ejecutando una instrucción RET.

12.2 Definir un procedimiento: PROC y ENDP.

Cada procedimiento debe estar definido entre las directivas PROC y ENDP, estas no generan código máquina, en su lugar PROC y ENDP son direcciones que le indican al ensamblador el inicio y el fin de un procedimiento.

(2)

nombre PROC tipo

El nombre es cualquier nombre que desees darle al procedimiento, diferente de un nombre reservado. El tipo puede ser NEAR y FAR. Si el procedimiento es llamado por un procedimiento que reside en un segmento diferente, debes especificar el tipo como FAR. De otra forma usa el tipo NEAR. Si no especificas un tipo, el tipo por omisión es NEAR. Usualmente sólo debes especificar el tipo de un procedimiento si es FAR. Es importante recordar que DOS inicia un programa de ensamblador llamando al programa principal. Entonces ya que DOS está en un segmento diferente, el procedimiento principal de cada programa debe ser de tipo FAR.

La directiva ENDP marca el final de cada procedimiento. El formato es: nombre ENDP

El nombre debe ser el mismo que tenga el principio del procedimiento, por ejemplo DISPLAY PROC

-- sentencias -- DISPLAY ENDP

Aquí hay un esqueleto de un procedimiento. El procedimiento se llama DISPLAY y es de tipo NEAR: ; inicio de procedimiento: DISPLAY

DISPLAY PROC ; -- sentencias ---

; fin de procedimiento: DISPLAY DISPLAY ENDP

12.3 El formato de un Procedimiento.

Estrictamente hablando, las directivas PROC y ENDP son todo lo necesario para definir un procedimiento. Sin embargo, hay otros estatutos que son usados de rutina.

Primero, es útil que cada procedimiento se imprima en una pagina nueva, use la directiva PAGE al principio del procedimiento.

Segundo, cada procedimiento debe estar documentado con los siguientes comentarios estándar: El nombre del procedimiento

El propósito del procedimiento

Que parámetros de entrada necesita el procedimiento Que salida genera el procedimiento

(3)

Tercero, cada procedimiento debe salvar y restaurar todos los registros como parte su función. Esto se hace con las instrucciones PUSHA y POPA (ver capitulo 9).

Cuarto, cada procedimiento debe finalizar con la instrucción RET para regresar al programa que hizo la llamada.

La figura 12-1 muestra el esqueleto de un procedimiento típico que puedes usar como modelo. El procedimiento se llama DISPLAY y es de tipo NEAR.

Figura 12-1 El esqueleto de un procedimiento PAGE

;--- ; DISPLAY

;

; Proposito:

; -el proposito del procedimiento va aquí ;

; Entrada:

; -la descripcion de la entrada va aqui ;

; Salida:

; -la descripción de la salida va aquí ;

; Procedimientos:

; -la descripción de los procedimientos a llamar va aquí

;--- ; inicio del procedimiento: DISPLAY

DISPLAY PROC ; salvar los registros PUSHA

; Las instrucciones del procedimiento van aquí ; restaurar registros

POPA

; regresar al procedimiento principal RET

; fin del procedimiento: DISPLAY DISPLAY ENDP

12.4 Llamar a un procedimiento: CALL.

Para llamar a un procedimiento, usa la instrucción CALL. Este es su formato: CALL dirección

La dirección puede ser directa o indirecta. Usualmente, la dirección es simplemente el nombre del procedimiento, por ejemplo:

(4)

Aquí hay un ejemplo de un direccionamiento indexado. En este ejemplo TABLA[BX] contiene la dirección del procedimiento;

CALL TABLA[BX]

Muchas de las veces es más simple que utilices el nombre, el direccionamiento indirecto e indexado te permite hacer llamadas más complejas. Estas se revisaran más adelante en la sección tablas de salto.

12.5 La dirección de Regreso.

Como mencione en el capítulo 3, la dirección de regreso es la locación a la cual el procedimiento regresa cuando su ejecución termina. Esta dirección de regreso es metida a la pila por la instrucción CALL. Así la instrucción CALL ejecuta lo siguiente:

Meter la dirección de regreso en la pila Saltar a la dirección del procedimiento.

Cuando el procedimiento que fue llamado regresa, el procesador espera encontrar la dirección de regreso en la pila.. El procesador pone la dirección de regreso como la dirección inmediata a la instrucción CALL.

Si una llamada a un procedimiento de tipo FAR, el procesador mete una dirección completa en la pila (dirección del segmento y offset).

12.6 Reglas para usa la instrucción CALL.

Existen dos reglas que debes seguir para indicarle al ensamblador si un procedimientos NEAR o FAR:

Primero, cuando definas un procedimiento que será llamado en otro segmento, debes especificar el procedimiento de tipo FAR.

Segundo, si hay alguna ambigüedad en la instrucción CALL, debes indicarle al ensamblador si la dirección representa una dirección completa (FAR) o sólo un desplazamiento (NEAR). Está situación se presenta cuando usas una direccionamiento indirecto, esto es, cuando el operando de la instrucción CALL contiene la dirección del campo dato que contiene la dirección del procedimiento.

Aquí hay un ejemplo. Digamos que declaras un campo dato llamado ADDR1 que contenga la dirección del procedimiento a llamar. Si el procedimiento es NEAR y su nombre es DISPLAY1, puedes definir la directiva del dato así:

ADDR1 DW DISPLAY1

El ensamblador puede inicializar ADDR1 para el offset de DISPLAY1. Debido a que el offset es una palabra en longitud, debemos usar la directiva DW para declarar ADDR1 como una palabra.

(5)

Sin embargo, digamos que tenemos otro campo dato, ADDR2, que contiene la dirección de un procedimiento FAR llamado DISPLAY2. Es este caso, ADDR2 debe contener la dirección del segmento y el offset. Así debemos definir ADDR2 como una palabra doble:

ADDR2 DD DISPLAY2

Si ADDR1 y ADDR2 contiene las direcciones de los procedimientos, podemos usar estos valores con direccionamiento indirecto. En otras palabras, debemos decirle al procesador que para llamar a los procedimientos sus direcciones están contenidas en ADDR1 y ADDR2. Para hacer esto, copia las direcciones a un registro especifico, encerrado entre paréntesis cuadrados, como el operando de la instrucción CALL. Usaremos el registro BX.

Debemos asegurarnos que el ensamblador sabe cuando BX está apuntando a una dirección NEAR (una palabra) o a una dirección FAR (una palabra doble). Para hacer esto, usamos el operador PTR. Si un direccionamiento indirecto apunta a un offset, usamos WORD PTR. Si un direccionamiento indirecto apunta a un dirección completa, usamos DWORD PTR.

Con el ejemplo anterior usamos un apuntador NEAR (ADDR1), puede usar la instrucción:

LEA BX,ADDR1

CALL WORD PTR [BX]

La primera instrucción copia el offset de ADDR1 a BX. La segunda llama al procedimiento cuya dirección está contenida en el campo dato al cual BX está apuntando. Debido a que está es una llamada NEAR, debemos decirle al ensamblador que el campo dato tiene una longitud de palabra.

Con el ejemplo del procedimiento apuntando a un FAR (ADDR2), podemos usar las siguientes instrucciones:

LEA BX,ADDR2

CALL DWORD PTR [BX]

Debido a que esta llamada es FAR, debemos especificar que el campo dato al cual BX esta apuntando es una palabra doble.

Esta técnica es útil si quieres obtener la dirección de una instrucción CALL mientras el programa se ejecuta.

12.7 Regresar de un procedimiento: RET.

Debes asegurarte que cada procedimiento regrese al lugar de donde fue llamado. (Hasta el programa principal que regresa a DOS). Para esto use la instrucción RET. Este es su formato:

RET valor-pop

El valor-pop es un valor inmediato descrito después en la sección intitulada “Sacar parámetros de la pila”. Muchas de las veces la instrucción RET no necesita operando.

La instrucción RET espera la dirección de regreso que está en el tope de la pila. La instrucción hace lo siguiente:

(6)

Saca la dirección de regreso de la pila Salta a la dirección de regreso

Si el procedimiento es de tipo FAR, el procesador debe sacar dos palabras de la pila. Si el procedimiento es de tipo NEAR, el procesador necesita sacar solo una palabra de la pila. El ensamblador coloca instrucciones diferentes RET cuando detecta si un procedimiento en NEAR o FAR, estas instrucciones le indican si tiene que recuperar una o dos palabras de la pila.

12.8 Puntos de Entrada y Salida.

Como mencione en el capítulo 6, la dirección en la cual el procedimiento inicia es llamado el punto de entrada. Cada procedimiento - incluyendo el programa principal - deben tener al menos un punto de entrada y una instrucción RET.

Es posible que un procedimiento tenga más de un punto de entrada pero no es recomendable.

Algunos procedimientos pueden tener uno o más puntos de salida lógica, como por ejemplo si se detecta una entrada invalida. En tales casos debes saltar a la instrucción RET al final del procedimiento, esto asegura que solo se tenga un punto de regreso.

Diseña tus procedimientos para que tengan un sólo punto de entrada y salida como lo muestra la figura 12-2.

Figura 12-2 Los puntos de entrada y salida

12.9 Paso de Parámetros en Registros.

Los procedimientos se comunican mandando datos de envío y regreso. Estos campos dato son llamados parámetros . Podemos decir que un procedimiento pasa parámetros de un procedimiento a otro. Un parámetro de entrada es pasado por el procedimiento que llama y un parámetro de salida es regresado cuando el procedimiento llamado termina.

Existen dos métodos estándar para paso de parámetros. Primero si hay pocos parámetros a pasar se pueden usar los registros. Por ejemplo digamos que el procedimiento ADD_LIST suma una lista de números. ADD_LIST requiere dos pedazos de información: el tamaño de la lista y la dirección de la lista. Un modo común es que reciba el tamaño en el registro AX y la dirección en el BX.

(7)

Digamos que las variables LIST y CONT representan la lista y su tamaño. La llamada al procedimiento podría ser así:

; poner los parametros de entrada para ADD_LIST MOV AX,CONT

LEA BX,LIST

; llamar al procedimiento

CALL ADD_LIST

Al principio del procedimiento ADD_LIST pondríamos estos comentarios: ; Entrada:

; AX -- el número de campos de la lista ; BX -- la dirección de la lista a ser sumada

Un procedimiento también puede pasar parámetros de salida en los registros. Sin embargo el procedimiento debe poner los parametros después de que los registros sean restaurados; o de otra manera el comando POPA sobrescribira los valores de los parámetros.

Por ejemplo, digamos que ADD_LIST guarda el resultado de la suma en la variable SUM. Este valor será pasado al procedimiento que invoco in el registro CX.

; restaurar registros

POPA ; actualizar el parámetro de salida

MOV CX,SUM ; regresar al procedimiento anterior

RET

Al principio del procedimiento habría estos comentarios: ; Salida:

; CX -- la suma de la lista

12.10 Pasar Parametros de Entrada por medio de la Pila.

Si no hay suficientes registros para pasar los parámetros lo mejor es utilizar la pila. El procedimiento que llama empuja los valores a la pila. El procedimiento invocado accesa la pila directamente, haciendo innecesario el uso de registros. El procedimiento llamado puede entonces modificar los valores en la pila en orden de pasar los parametros de regreso al procedimiento que llamo. Se deben meter los parametros en la pila antes de hacer la llamada. Aquí hay un ejemplo de la llamada a un procedimiento, llamado CALCULO, el cual requiere 6 parametros. Los parametros son nombrados de p1 a p6, y en la práctica pueden ser registros, campos dato o valores inmediatos.

; poner parametros de entrada para CALCULO PUSH P1

PUSH P2 PUSH P3

(8)

PUSH P4 PUSH P5 PUSH P6 ; llamar a CALCULO

CALL CALCULO

12.11 Usar el registro BP para accesar los Parametros de la Pila.

Accesar los parametros de la pila es más complicado. Esto es debido a que la instrucción CALL ejecutada después de la introducción de los parametros de entrada. Esto significa que el procedimiento invocado no puede sacar de la pila los parametros sin perder la dirección de regreso.

Por supuesto, es posible sacar la dirección de regreso, guardarla y después recuperar los parametros, sin embargo hay una forma más sutil de hacer esto, la cual es accesar la pila directamente usando el registro BP.

Esto funciona así, al principio del procedimiento invocado, la dirección del tope de la pila se copia al registro BP usando la instrucción:

MOV BP,SP

El procedimiento puede usar ahora a BP como la dirección base de la pila. En particular, los parametros pueden ser seleccionados como [BP+2], [BP+4], [BP+6], etc.

Si la pila consiste de una lista de palabras con una en su propio espacio. El registro SS mantiene la dirección del segmento. El registro SP contiene un offset en el segmento. SP apunta al tope de la pila. El primer push copia datos a la palabra en la dirección más alta, el siguiente push copia datos en la siguiente dirección más alta, y así la pila crece hacia abajo hasta su base una palabra a la vez.

La figura 12-3 muestra la pila con seis parámetros de entrada, un procedimiento ha sido llamado y BP está puesto a SP.

Figura 12-3 Seis parametros en la pila

La figura 12-3 muestra a la pila después de haber metido seis parametros a ella, un procedimiento ha sido invocado, y BP y SP están direccionadas.

(9)

Como puedes ver, Una vez que BP está puesta a SP, los parámetros pueden ser accesados como SS:[BP+2], SS:[BP+4], etc. Entonces el procesador asume que te refieres al segmento de pila y usa BP con su incremento como offset.

Una vez que BP está direccionado, el procedimiento puede empujar más datos en la pila y cambiando el valor de SP. Sin afectar el acceso a los parametros. De hecho esto sucede cuando la instrucción PUSHA salva los registros al inicio de un procedimiento.

Esto es ilustrado en la figura 12-4. El diagrama muestra a la misma pila después de que el procedimiento ha metido los valores de los registros. Note que BP permanece sin cambio para el acceso a los parametros.

Figura 12-4 BP sin cambio en la Pila

Para ilustrar esto, aquí hay un ejemplo de algunas instrucciones que estarían al inicio de un procedimiento que espera seis parametros metidos en la pila. Este ejemplo copia los valores de los parametros a los registros. Note que BP debe ser copiado a BP antes de salvar los registros, (debido a que el comando PUSHA cambia el valor de SP):

; inicio del procedimiento: CALCULO CALCULO PROC

; apuntar BP al tope de la pila MOV BP,SP ; salvar registros

PUSHA

; copiar los parametros de entrada a los registros

MOV AX,[BP+12] MOV BX,[BP+10] MOV CX,[BP+8] MOV DX,[BP+6] MOV DI,[BP+4] MOV SI,[BP+2]

(10)

Un último punto: La discusión anterior supone que el procedimiento es de tipo NEAR. En otras palabras, la dirección de regreso ocupa 1 palabra en la pila, si el procedimiento fuera FAR, la dirección de regreso sería de 2 palabras en la pila, y las direcciones de los parámetros serían [SP+4], [SP+6], [SP+8], etc.

12.12 Regresar Parametros usando la Pila.

Es posible usar la pila para regresar parametros de salida al procedimiento que llamo. Sin embargo no es posible meterlos a la pila, ya que quedarían sobre la dirección de regreso. Mejor usa directamente el registro BP para accesar la pila directamente y modificar los parametros de entrada directamente. Cuando regrese el procedimiento que llamo puede examinar esos valores.

Aquí hay un ejemplo usando los procedimientos MAIN y CALCULO descritos antes. MAIN empuja seis parametros en la pila. CALCULO usa estos valores para ejecutar los cálculos y entonces copia los resultados en el último parámetro metido a la pila, usando BP para accesarla directamente.

Si AX contiene el valor del parámetro de salida, la secuencia puede ser así: ; salvar el parámetro de entrada

MOV [BP+2],AX ; restaurar los registros

POPA ; regresar al MAIN

RET

Así, la entrada de la pila que reside en [BP+2] ha sido transformada de parámetro de entrada a parámetro de salida. Después del retorno, MAIN podría sacar de la pila para examinar el resultado. Así su secuencia podría ser:

; poner los parametros de entrada en la pila PUSH P1 PUSH P2 PUSH P3 PUSH P4 PUSH P5 PUSH P6 ; llamar al procedimiento CALCULO CALL CALCULO ; copiar el resultado a AX

POP AX

; sacar el resto de los parametros de entrada

POP TEMP

POP TEMP

POP TEMP

POP TEMP

(11)

Note que el MAIN debe sacar todos los parámetros que metió, en este caso con un campo dato temporal TEMP.

12.13 Sacar parametros de la Pila

En el último ejemplo de la sección anterior, el programa que hacia la llamada examinaba los parametros de salida sacando de la pila después del regreso. Sin embargo en muchos casos, la pila puede contener parametros de entrada pero no de salida.

En tales casos la instrucción RET puede sacar varios valores de la pila automáticamente como parte del regreso, puedes hacer esto especificando el número de bytes a ser sacados con un operando inmediato. El formato de la instrucción RET es:

RET valor-pop

Donde valor-pop es el número de bytes a ser sacados durante el retorno.

Aquí hay un ejemplo. Digamos que el procedimiento llamado MAIN llama a un procedimiento llamado HAZ_TABLA para construir cierto tipo de tabla. HAZ_TABLA requiere seis parametros de entrada y ninguno de salida.

; poner los parametros de entrada para HAZ_TABLA PUSH P1 PUSH P2 PUSH P3 PUSH P4 PUSH P5 PUSH P6

; llamar a HAZ_TABLA para construir la tabla CALL HAZ_TABLA

Al final de HAZ_TABLA, puedes usar el siguiente estatuto para regresar al procedimiento MAIN: ; sacar los parametros de la pila y regresar a MAIN

RET 12

Este RET no solo regresa al procedimiento anterior, también saca 12 bytes de la pila. Por supuesto esto debe estar perfectamente coordinado. La instrucción RET al final del procedimiento HAZ_TABLA debe sacar el mismo número de bytes que fueron empujados por MAIN.

Para referencia, aquí están los pasos que sigue un RET normal Sacar la dirección de regreso

Saltar a la dirección de regreso

(12)

Sacar la dirección de regreso

Sacar el número de bytes requeridos Saltar a la dirección de regreso

12.14 Usar la bandera de Acarreo para indicar un Error.

Ya aprendimos dos modos de pasar información entre procedimientos, usando registros o la pila. Estos dos métodos son buenos para pasar datos. Sin embargo, existe un pedazo de información que puedes querer pasar del procedimiento llamado al que hizo la llamada: ¿Como se si todo termino satisfactoriamente?

El modo estándar de determinar esto es limpiando a activando la bandera de acarreo. Si el procedimiento llamado termino exitosamente, ponemos CF a 0. Si un error ocurrió ponemos CF a 1. Así el procedimiento que hizo la llamada puede cerciorarse si todo salió bien.

Aquí hay un ejemplo. Un procedimiento MAIN llama a otro procedimiento READ_DATA para leer algunos datos de un archivo. Si la operación tuvo éxito READ_DATA regresa 0 en CF. Si la operación fallo por alguna causa, READ_DATA pone CF a 1.

Aquí hay algunos estatutos que podrías usar al final de READ_DATA para implementar el manejo de errores:

; indicar que un error ha ocurrido BAD_DATA:

STC

JMP RETURN_READ_DATA

; indicar que no ocurrió ningún error GOOD_DATA:

CLC

; restaurar los registros y regresar al MAIN POPA

RET

Dentro del procedimiento MAIN, digamos que un mensaje de error será desplegado sin los datos no se pueden leer.

; poner los parametros de entrada

-- las instrucciones de entrada van aquí --- ; llamar a READ_DATA para leer un archivo

CALL READ_DATA ; checar el error

(13)

Bajo algunas circunstancias, no es suficiente saber que un error ocurrió, sino saber que tipo de error fue puedes regresar un código de error en algún registro para que el procedimiento principal comunique cual fue la falla.

12.15 Tablas de Salto.

Una tabla de salto es una lista de direcciones, cada cual representa una locación a la cual un programa puede saltar. La tabla de saltos son usadas comúnmente para guardar una lista de direcciones de varios procedimientos. Un programa puede usar tal tabla para escoger el procedimiento a llamar basado en las condiciones actuales.

Aquí hay un ejemplo. Digamos que un programa procesa algunos datos y entonces escribe los datos como salida. El programa puede escribir los datos a la impresora, pantalla, o a disco. Los procedimientos que hacen esto son WRITE_PRINTER, WRITE_DISPLAY y WRITE_FILE respectivamente.

Después los datos son procesados, el programa le pide al usuario que teclee el número del dispositivo de salida. Este número es grabado en la variable OPCION, 1 para impresora, 2 para pantalla y 3 a disco. Así el programa necesita llamar a uno de tres procedimientos, basados en el valor de OPCION.

Un modo de hacer esto es usar un CASE: ; case OPCION=1: llamar a WRITE_PRINTER ; case OPCION=2: llamar a WRITE_DISPLAY ; case OPCION=3: llamar a WRITE_FILE

CMP OPCION,1 JE L1 CMP OPCION,2 JE L2 CMP OPCION,3 JE L3 JMP L4 L1: ; opcion=1 CALL WRITE_PRINTER JMP L4 L2: ; opcion=2 CALL WRITE_DISPLAY JMP L4 L3: ; opcion=3 CALL WRITE_FILE L4:

Un método simple es usar una tabla de saltos. Primero ponemos una tabla en el segmento de datos que contenga los offsets de los tres procedimientos:

JUMP_TABLE LABEL WORD

DW WRITE_PRINTER

DW WRITE_DISPLAY

DW WRITE_FILE

Puedes hacer referencia a la dirección de estos procedimientos relativa al valor de JUMP_TABLE. Esto es, basados en el valor de OPCION, podemos llamar al procedimiento correcto como sigue:

(14)

OPCION Usa la dirección en 1 JUMP_TABLE+0 2 JUMP_TABLE+2 3 JUMP_TABLE+4

Aquí está el plan. Podemos copiar la dirección de la tabla de datos a un registro (BX por ejemplo). Después adicionamos 0, 2 o 4 al registro, dependiendo del valor de OPCION. Finalmente debemos usar el contenido del registro con un direccionamiento indirecto para llamar al procedimiento apropiado.

Para copiar la dirección de la tabla de salto en BX, podemos usar la instrucción LEA

LEA BX,JUMP_TABLE

Ahora, necesitamos adicionar 0,2 o 4 a BX dependiendo del valor de OPCION. La forma más fácil es copiar OPCION al registro AX, restarle 1 y multiplicarlo por 2:

MOV AX,OPCION

DEC AX

SHL AX,1 ADD BX,AX

Las instrucciones serán descritas en capítulos posteriores.

Ahora BX apunta a la dirección correcta. Puedes usar direccionamiento indirecto para hacer la llamada:

CALL WORD PTR [BX]

Pongamos todo junto:

; escribir a un dispositivo seleccionado por OPCION ; 1 -> impresora (llama a WRITE_PRINTER) ; 2 -> pantalla (llama a WRITE_DISPLAY) ; 3 -> archivo (llama a WRITE_FILE)

LEA BX,JUMP_TABLE MOV AX,OPCION DEC AX SHL AX,1 ADD BX,AX CALL WORD PTR [BX]

Note que es mucho más fácil que usar el constructor CASE.

Si vas a usar tablas de salto, hay dos puntos importantes que debes comprender. Primero, en el ejemplo anterior supusimos que el procedimiento es NEAR, por eso accesamos la tabla con multiplos de 2.

Si usas una tabla de saltos con direcciones FAR, debes hacer tres cambios. Definir la tabla de datos con DD (palabras dobles). accesar la tabla de saltos en multiplos de 4. Puedes hacer esto con la instrucción SHL así

SHL AX,2

(15)

CALL DWORD PTR [BX]

La segunda cosa importante que debes cmprender es que el valor que tenga opción siempre debe corresponder a algunas de las opciones en la tabla de salto, impidiendo que el selector tenga un valor invalido.

12.16 Linkear programas que están separados en varias partes.

Cuando escribes un programa grande, es útil poder ensamblar el programa separadamente. Por ejemplo digamos que un programa grande tiene 30 procedimientos. Si uno de los procedimientos tuviera que ser cambiado ocasionalmente tendríamos que rensamblar el programa completo.

La solución es dividir el programa en dos partes. Un aparte mantiene el procedimiento que necesitamos cambiar, y las otras partes el resto del programa. Puedes ensamblar ambas partes separadamente y usar el linker para encadenar los dos modulos objeto en un sólo ejecutable.

Aquí hay un ejemplo, digamos que tienes un programa con varios procedimientos. El archivo SMALL.ASM contiene un procedimiento y el archivo LARGE.ASM contiene el resto del programa. Para crear un load module, debes seguir los siguientes pasos:

Primero esnambla ambos programas tecleando MASM LARGE;

MASM SMALL;

Asumiendo que los archivos están bien, obtendremos dos modulos objeto: LARGE.OBJ y SMALL.OBJ. Para correr el programa, necesitas crear un load module con estos obj´s. Para hacer esto teclea:

LINK LARGE+SMALL,PROGRAM;

Este comando crea un programa ejecutable con el nombre PROGRAM.EXE

Cuando necesites cambiar el procedimiento, solo necesitas rensamblar SMALL.ASM tecleando MASM SMALL;

No necesitas rensamblar a LARGE.ASM ya que aún tienes por ahi el obj llamado LARGE.OBJ, todo lo que necesitas es volver a encadenar el programa completo

LINK LARGE+SMALL,PROGRAM;

12.17 Como escribir programas que tienen partes separadas.

Si puedes dividir un programa en partes separadas, cada una consiste de uno o más segmentos cada uno almacenado en su propio archivo. La estructura de sus partes es muy parecida a lo que sería el mismo programa contenido en un archivo grande. La diferencia principal es que debes decirle al ensamblador cuales nombre de una parte serán referenciados en otra. Para hacer esto, puedes usar las directivas EXTRN y PUBLIC.

(16)

Estas dos directivas son complementarias. EXTRN le dice al ensamblador cuales nombres serán definidos en una parte diferente de un programa (esto es, cuales nombres son externos). PUBLIC le dice al ensamblador cuales nombres pueden ser referenciados por otras partes del programa. La idea es que cada referencia externa requiere dos directivas: una directiva PUBLIC, donde el nombre está definido, y una directiva EXTRN, donde el nombre es usado.

El formato de la directiva EXTRN es:

EXTRN nombre:tipo

Si el nombre representa un campo dato, el tipo debe ser u n BYTE; WORD, DWORD, QWORD, y TBYTE. Si el nombre representa una etiqueta, el tipo debe ser NEAR o FAR. El tipo es necesario ya que le dice al ensamblador como trabajar con el nombre.

Aquí hay dos ejemplos:

EXTRN MSG2:BYTE

EXTRN EXTERNAL_PROCEDURE:FAR

El primer ejemplo le dice al ensamblador que el nombre MSG2 representa un campo dato (de tipo byte) que está definido externamente de está parte del programa. El segundo ejemplo le dice al ensamblador que EXTERNAL_PROCEDURE representa un procedimiento (de tipo far) que está definido externamente para esa parte del programa.

Una vez que un nombre aparece en un directiva EXTRN, puedes usarlo sin volverlo a definir y el ensamblador no considerará la referencia como un error. El ensamblador asume que todas las referencias a ser definidas externamente serán resueltas por el linker.

Cuando un programa usa referencias externas, el ensamblador pone un mensaje al inicio del modulo objeto. Este mensaje le dice al linker cuales nombres serán definidos en otros módulos objeto.

El formato de la directiva PUBLIC es:

PUBLIC nombre

Esta directiva identifica nombre que pueden ser usados como referencias externas en otras partes del programa. Aquí hay dos ejemplos:

PUBLIC MSG2

PUBLIC EXTERNAL_PROCEDURE

Cuando un programa declara tales nombres, el ensamblador pone un mensaje al inicio del modulo objeto. El mensaje le dice al linker cuales nombres de su modulo objeto serán usados como referencias externas por otros módulos objeto.

Como regla general, es buena idea juntar todas las directivas EXTRN y PUBLIC. Esto asegura que el ensamblador conoce todos los nombres especiales antes de que aparezcan en el programa.

La figura 12-6 y 12-7 muestran las dos partes de un programa que puede ensamblarse separadamente y juntos. La primer parte consiste del segmento de pila, un segmento de datos, y el segmento de código conteniendo el programa principal. La segunda parte consiste de un segmento de código conteniendo el procedimiento llamado EXTERNAL_PROCEDURE.

(17)

La lógica del programa principal es: Despliega el mensaje (MSG1).

Llama a EXTERNAL_PROCEDURE. Despliega el mensaje (MSG1) de nuevo.

El procedimiento EXTERNAL_PROCEDURE despliega un mensaje por sí mismo. El proceso para obtener el programa ejecutable es el siguiente:

MASM FIRST; MASM SECOND;

Ahora linkeamos los dos programas juntos: LINK FIRST+SECOND,PROGRAM;

Si ejecutas el programa PROGRAM veras los siguiente mensajes: Hello from program #1.

Hello from program #2. Hello from program #1.

Figura 12-6 Programa FIRST primera parte del programa completo. PAGE 58,132

;--- ; programa para demostrar el uso de referencias externas

; --- ; ajustar el titulo y el conjunto de instrucciones

TITLE programa que usa referencias externas .286

;--- segmento STACK SSEG SEGMENT STACK

DB 32 DUP(“STACK---”) SSEG ENDS ;--- nombres EXTERNAL EXTRN EXTERNAL_PROCEDURE:FAR ;--- nombres PUBLIC PUBLIC MSG2 ;--- segmento DATA DSEG SEGMENT

MSG1 DB “Hello from procedure # 1”, 0DH, 0AH,”$” MSG2 DB “Hello from procedure # 2”, 0DH, 0AH,”$” DSEG ENDS

;--- segmento CODE

CSEG SEGMENT ‘CODE’

ASSUME CS:CSEG, SS:SSEG; DS:DSEG, ES:DSEG PAGE

;--- ; MAIN (programa principal)

(18)

; Proposito:

; Demostrar el uso de referencias externas ;

; Entrada:

; -- ninguna -- ;

; Salida:

; Tres mensajes son desplegados en la pantalla ;

; Nombres Externos:

; EXTERNAL_PROCEDURE (peocedimiento) - despliega un mensaje; ;--- ; Procedimiento: MAIN

MAIN PROC FAR ; Salvar la direccion para poder regresar a DOS PUSH DS PUSH 0 ; Actualizar el registro de segmento

MOV AX,DSEG MOV DS,AX MOV ES,AX ; Desplegar mensaje #1 MOV AX,9H LEA DX,MSG1 INT 21H

; Llamar al procedimiento externo para imprimir el mensaje # 2 CALL EXTERNAL_PROCEDURE ; Desplegar mensaje #1 otra vez

MOV AX,9H

LEA DX,MSG1

INT 21H ; Regresar a DOS

RET ; Fin de procedimiento: MAIN

MAIN ENDP

; Fin de segmento de código CSEG ENDS

;--- Fin de programa

END MAIN

Figura 12-7 Segunda parte del programa en dos partes SECOND PAGE 58,132

;--- ; un ejemplo de un procedimiento externo

;--- ; ajustar el titulo y el conjunto de instrucciones

TITLE programa externo

.286

(19)

EXTRN MSG2:BYTE

;--- nombres PUBLIC

PUBLIC EXTERNAL_PROCEDURE

;--- segmento CODE

CSEG SEGMENT ‘CODE’

ASSUME CS:CSEG PAGE ;--- ; EXTERNAL_PROCEDURE ; ; Proposito:

; Demostrar como funciona un procedimiento externo ;

; Entrada:

; -ninguna- ;

; Salida:

; El mensaje se despliega en pantalla ;

; Nombre externos:

; MSG2 (dato - el mensaje deplegado)

;--- ; inicio del procedimiento: EXTERNAL_PROCEDURE

EXTERNAL_PROCEDURE PROC FAR

; salvar los registros PUSHA ; despliega el segundo mensaje

MOV AH,9H

LEA DX,MSG2

INT 21H ; restaurar registros

POPA ; regresar al procedimiento principal

RET

; fin del procedimiento: EXTERNAL_PROCEDURE EXTERNAL_PROCEDURE ENDP

; fin de segmento de código CSEG ENDS ; fin de programa

END

{_____________o___________}

(20)

Este capítulo cubre la aritmética de números con signo y sin signo, establecidas en el capítulo 8. Sin embargo los números de punto flotante (notación científica) y los números de punto fijo requieren tipos de hardware diferente.

El procesador principal sólo puede trabajar con números de punto fijo. Dicho de otra manera el procesador no trabaja con números con fracciones a menos que los simules con números enteros. Por tanto para el curso de ensamblador básico solo cubriré los números con signo.

Nuevos Términos

Número de punto fijo. Un número en el cual el punto decimal está fijo, también llamados números enteros.

Números de punto flotante. Un número en el cual su punto decimal puede estar en cualquier posición; también llamados números fraccionales.

Número sin signo. Un número de punto fijo cuyo valor puede ser positivo o cero.

Número con signo. Un número de punto fijo cuyo valor puede ser positivo negativo o cero. Complemento. Un patrón de bits que tiene exactamente los valores opuestos a otro patrón de bits. Complemento a dos. Un número que se obtiene al complementar un patrón de bitas y sumarle 1.

13.1 Números con signo y sin signo.

Existen dos tipos de números de punto fijo: números sin signo que pueden representar valores positivos y el cero; y números con signo que pueden representar números positivos y negativos incluyendo al cero.

Los números con signo y sin signo pueden ser almacenados en bytes o palabras. Los números con signo son almacenados en su equivalente en binario. Por ejemplo el equivalente binario de 57 es 1110001B. Si su valor fuera almacenado en un byte quedaría como:

00111001

o como una palabra: 0000000000111001

Para encontrar el rango de posibilidades para los números con signo, podemos examinar el patrón de bits menor y mayor. Para números con signo para bytes sería

00000000B a 11111111B esto es de 0 a 255.

Para números sin signo almacenados como palabras, el rango es: 0000000000000000B a 1111111111111111B esto es de 0 a 65535.

(21)

Tabla 13-1 Los rangos de los números con signo y sin signo

Unidad de almacenamiento Sin signo Con signo

Byte 0 a 255 -128 a 127

Word 0 a 65535 -32768 a 32767

13.2 El sistema del complemento a dos.

Representar números con signo requiere un esquema números positivos, negativos y al cero. Podría parecer simple que dejáramos el bit de más a la izquierda del byte o la palabra para indicar el signo: 0 para positivos y 1 para negativos. Los otros bits representan la magnitud del número.

Por ejemplo, considere almacenar los valores +57 y -57 en un byte. Si usamos el esquema descrito anteriormente, tenemos:

+57 ---> 00111001 -57 ---> 10111001

Desafortunadamente, hay problemas con este esquema. La más importante es que hay dos posibles representaciones del cero:

00000000 y 10000000

Para resolver este problema, los diseñadores de computadoras usan un esquema llamado complemento a dos. Sin embargo antes de que comprendas este esquema, debes entender el término complemento de bits. El complemento de un patrón de bits es el patrón que contiene exactamente sus valores opuestos; cada 0 es cambiado a 1 y cada 1 por un cero. Aquí hay algunos ejemplos.

Patrón de bits Complemento 01110 10001 1010101 0101010

000 111

11111111 00000000

Para calcular el complemento a dos de un número, debes obtener su complemento y sumarle 1. Por ejemplo el complemento a dos de 00111001B (57 decimal):

patrón original 00111001

complemento 11000110

+ 1

comp. a dos 11000111

Esta regla puede ser usada para obtener el complemento a dos de cualquier patrón de bits, sin embargo hay una regla extra que debes considerar cuando calculas el complemento a dos de cero.

patrón original 00000000

complemento 11111111

+ 1 100000000

(22)

13.3 Usar el complemento a dos para representar números con signo.

El procesador utiliza el complemento a dos para representar números con signo. El esquema es que, mientras un número positivo es representado por su valor binario, un número negativo es representado por el complemento a dos de su número positivo. El bit de más a la izquierda indica el signo: 0 positivo 1 negativo.

En otras palabras, el negativo de un número es su complemento a dos. Aquí hay algunos ejemplos de valores de 8 bits.

Valor decimal valor binario complemento a dos

57 00111001 11000111

-57 11000111 00111001 127 01111111 10000001 -127 10000001 01111111

0 00000000 00000000

13.4 Descifrando números en complemento a dos.

Cuando escribes un programa el complemento a dos de los números negativos no representa ningún problema, debido a que puede usar decimales con signo cuando es necesario. Sin embargo cuando pruebas un programa con un debugger, necesitas interpretar los valores negativos en los registros y locaciones de memoria. En tales casos, necesitas calcular el complemento a dos del número para encontrar valor absoluto.

Aquí hay un ejemplo. Digamos que pruebas un programa con números con signo, y AX contiene el siguiente patrón de bits:

1001010110100010B

Sabes que el numero es negativo ya que empieza con 1, para encontrar su valor calculas el complemento a dos.

patrón original 1001010110100010 complemento 0110101001011101

+ 1 comp. a dos 0110101001011110

Convirtiendo el complemento a dos a decimal encontramos que su valor es 27230. Así el numero original tenia el valor -27230.

Por supuesto estos valores en el debugger estarán en hexadecimal. Debes convertir estos valores a binario hacer el complemento y después obtener su valor en decimal.

Aquí hay un ejemplo: El programa trabaja con números con signo y el registro AX contiene C04AH: C 0 4 A

(23)

1100 0000 0100 1010

Después obtenemos el complemento a dos patrón de bits 1100000001001010 complemento 0011111110110101

+ 1 comp. a dos 0011111110110110

El numero en decimal es 16310, o sea -16310 originalmente

Digamos que una palabra tiene la primera parte del numero con signo en la locación 10A0H y su segunda palabra en la locación posterior si estos valores fueran 5CH y B7H respectivamente cual es el numero que representa: B 7 5 C 1011 0111 0101 1100 patrón original 1011011101011100 complemento 0100100010100011 + 1 comp. a dos 0100100010100100 en decimal era el -18596.

Puedes usar la siguiente regla para los dígitos hexadecimales. Un número con signo en hex es positivo si empieza con los dígitos:

0 1 2 3 4 5 6 7 Y es negativo si empieza con los dígitos: 8 9 A B C D E F

13.5 Usar saltos condicionales con números con signo y sin signo.

Existen un conjunto de saltos condicionales para números con signo y otro para números sin signo. Primero que todo debes asegurarte que el valor del número no sobrepasa el rango permitido. Para hacer esto, usa el salto condicional apropiado después de la operación aritmética.

Con números sin signo, las instrucciones JC y JNC prueban las condiciones de rebose: JC salta si el valor sin signo se sobrepaso

JNC salta si el valor sin signo no se sobrepaso

Estas instrucciones prueban la bandera de acarreo (CF), si el valor se sobrepaso la bandera CF esta puesta a 1 , sino la bandera esta a 0.

Con números con signo, las instrucciones JO y JNO prueban las condición de sobreflujo: JO salta si el valor con signo se sobrepaso

(24)

JNO salta si el valor con signo no se sobrepaso

Estas instrucciones prueban la bandera de sobreflujo (OF), si el valor se sobrepaso OF esta 1, sino esta a 0.

Por ejemplo digamos que quieres sumar la variable VALOR al registro AX y después comprobar si el resultado no fue muy grande. si lo fue saltamos a la etiqueta de ERROR. Si estas usando números con signo puedes usar

ADD AX,VALOR

JC ERROR

Si estas usando números sin signo puedes usar ADD AX,VALOR

JO ERROR

La tabla 13-3 muestra los saltos condicionales disponibles para ambos tipos de números. 13-3 Saltos condicionales para números sin signo

Opcode Significado

JC salta si sobreflujo JNC salta si no sobreflujo

JB (JNAE) salta si debajo (no arriba ni igual) JA (JNBE) salta si arriba (no abajo o igual) JBE (JNA) salta si debajo o igual (no arriba) JAE (JNB) salta si arriba o igual (no abajo)

JE salta si igual

JNE salta si no igual Saltos condicionales para números con signo

Opcode Significado

JO salta si sobreflujo JNO salta si no sobreflujo

JL (JNGE) salta si menor que (no mayor que o igual) JG (JNLE) salta si mayor que (no menor o igual) JLE (JNL) salta si menor o igual (no mayor que) JGE (JNL) salta si mayor o igual (no menor)

JE salta si igual

JNE salta si no igual

Cuando comparas dos números, asegúrate de usar el salto condicional adecuado. Por ejemplo, si quieres comparar el valor del registro AX con al variable TOTAL sin signo. Digamos que quieres saltar si AX e mayor que TOTAL puedes usar.

CMP AX,TOTAL

JA L1

Si estas usando números con signo puedes usar:

CMP AX,TOTAL

(25)

El último tipo de salto condicional se aplica solo a los números con signo. Puedes usar las instrucciones JS y JNS para checar la bandera de signo (SF)

JS salta si SF esta puesta a 1 JNS salta si SF esta puesta a 0

Si el resultado de la operación previa fue positivo SF tendrá un valor de 0. Si el resultado es negativo SF tendrá un 1.

13.6 Adición y substracción: ADD y SUB.

La suma y resta son conceptualmente simples. Usa la instrucciones ADD y SUB. El formato es:

ADD operando1, operando2

SUB operando1, operando2

El segundo operando es sumado o restado del primero. El resultado es almacenado en el primer operando, remplazando el valor original.

Aquí hay algunos ejemplos:

ADD AX,VALOR

SUB AX,VALOR

En el primer ejemplo la instrucción ADD calcula (AX+VALOR) y guarda el resultado en AX. La segunda instrucción resta (AX-VALOR) y guarda también el resultado en AX.

Los operandos deben ser del mismo tipo: ambos bytes o palabras. El primer operando puede ser un registro, o un campo dato (locación de memoria). El segundo operando puede ser un registro, un campo dato, o un valor inmediato. Sin embargo no puedes sumar directamente dos campos dato

ADD VALOR1,VALOR2

Si VALOR1 y VALOR2 son campos dato, debes copiar uno a un registro: MOV AX,VALOR2

ADD VALOR1,AX

13.7 Sumar y restar una palabra y un byte.

Las instrucciones ADD y SUB requieren que ambos operandos sean de la misma longitud, ya sea byte o palabra. Sin embargo, es útil poder restar o sumar una palabra y un byte. en tales casos, necesitas expander el byte a una palabra. Los pasos son los siguientes:

1. Copiar el byte a la parte derecha de AX.

(26)

3. Usar AX en lugar del byte en el calculo.

Aquí hay un ejemplo. Digamos que quieres sumar dos campos dato BYTE_VALOR de un byte y WORD_VALOR de una palabra. Si son números sin signo, las instrucciones son

MOV AL,BYTE_VALOR MOV AH,0

ADD AX,WORD_VALOR

Si estas trabajando con números con signo, la instrucción CBW puede ayudarte a poner AH al valor complementado en dos apropiado. La instrucción CBW puede entenderse como “convertir byte a word”. Así, si quieres hacer lo mismo que en el ejemplo anterior usando números con signo:

MOV AL,BYTE_VALOR CBW

ADD AX,WORD_VALOR

CBW trabaja solo con AH y AL. La instrucción examina el valor de AL y hace que AX tenga el mismo valor complementado de AL. Si AL es positivo AH esta como cero, si AL es negativo, AH esta puesta toda a unos.

13.8 Sumar y Restar números muy grandes: ADC y SBB.

Algunas veces necesitas sumar o restar números que son mucho mayores que los rangos de bytes o palabras, aunque no tengas coprocesador puedes utilizar las palabras dobles y cuádruples para estas operaciones. Sin embargo tendrás que usar instrucciones regulares y trabajar sobre los números grandes una palabra a la vez. En tales casos puedes usar las directivas DD y DQ para definir los números.

Aquí hay un ejemplo del uso de palabras dobles. Quieres sumar 123456789 a 987654321. Define los campos dato como sigue:

DFIRST DD 987654321

DSECOND DD 123456789

En hex, los valores son 3ADE68B1H Y 075BCD15H. Aquí esta almacenados los valores en las palabras dobles:

Note que los bytes están almacenados en orden inverso. Las dos palabras de DFIRST pueden ser direccionadas como DFIRST y DFIRST+2; las dos palabras de DSECOND pueden ser direccionadas como DSECOND y DSECOND+2. Para ejecutar la suma, debemos sumar una palabra a la vez. Como los bytes están almacenados en orden inverso, debes sumar de izquierda a derecha. Los pasos son los siguientes:

Sumar la palabra de la izquierda de DSECOND en la palabra de la izquierda de DFIRST. Sumar la palabra de la derecha de DSECOND a la palabra de la derecha de DFIRST.

(27)

La primera suma puede generar un acarreo. Si es así el acarreo debe ser sumado a la segunda suma. La instrucción ADD siempre avisa si hay un acarreo activando la bandera CF.

Hay una instrucción que hace este trabajo llamada ADC, la cual significa suma y acarrea. Así puedes usar ADC para hacer las sumas izquierdas y ADC para las derechas. Veamos el código.

; sumar palabras dobles: DFIRST<---DFIRST+DSECOND

MOV AX,WORD PTR DSECOND

ADD WORD PTR DFIRST,AX

MOV AX,WORD PTR SECOND+2

ADC WORD PTR DFIRST+2,AX

Nota- La instrucción MOV no modifica el valor de CF.

Si quieres usar palabras cuádruples, el patrón es el mismo. Como las palabras, el ensamblador almacena las palabras dobles y cuádruples en orden inverso. Primero define dos campos dato con su valores:

QFIRST DQ 543210987654321

QSECOND DQ 123456789012345

Ahora, sume las dos números una palabra a la vez, de izquierda a derecha. Use ADD para la primera adición y use ADC para todas las subsecuentes adiciones y sus acarreos.

; sumar quadwords: QFIRST<-- QFIRST+QSECOND

MOV AX,WORD PTR QSECOND

ADD WORD PTR QFIRST,AX

MOV AX,WORD PTR QSECOND+2

ADC WORD PTR QFIRST+2,AX

MOV AX,WORD PTR QSECOND+4

ADC WORD PTR QFIRST+4,AX

MOV AX,WORD PTR QSECOND+6

ADC WORD PTR QFIRST+6,AX

La substracción es similar a la adición. La diferencia es que no hay acarreos sino prestamos. Con la resta CF esta activa a 1 si hay un préstamo en la posición de más a la izquierda; sino CF esta puesta a 0.

La instrucción SBB es análoga al ADC. El valor de CF es tomado en cuenta durante la resta. Así puedes restar números grandes una palabra a la vez de izquierda a derecha usando SUB para la primera resta y SBB para la demás. Aquí hay dos ejemplos, que muestran como restar palabras dobles y cuádruples. ; restar palabras dobles: DFIRST<-- DFIRST-DSECOND

MOV AX,WORD PTR DSECOND

SUB WORD PTR DFIRST,AX

MOV AX,WORD PTR DSECOND+2

SBB WORD PTR DFIRST+2,AX

; restar palabras cuádruples: QFIRST<-- QFIRST-QSECOND

MOV AX,WORD PTR QSECOND

SUB WORD PTR QFIRST,AX

MOV AX,WORD PTR DSECOND+2

SBB WORD PTR QFIRST+2,AX

(28)

SBB WORD PTR QFIRST+4,AX

MOV AX,WORD PTR QSECOND+6

SBB WORD PTR QFIRST+6,AX

13.9 Sumar y restar 1 de un número: INC y DEC.

El procesador tiene dos instrucciones que pueden incrementar o decrementar 1 a un numero estas son INC y DEC. Su formato es:

INC operando

DEC operando

El operando puede ser un campo dato o un registro. Aquí hay algunos ejemplos: INC AX DEC AL INC CH DEC DI INC TOTAL DEC TABLE[BX]+4

INC y DEC son usadas especialmente con ciclos cuyos valores deben ser incrementados o decrementados en 1 cada vez que se ejecute el ciclo. El siguiente ejemplo tiene un ciclo que llama al procedimiento READ_CARACTER hasta que la bandera de acarreo CF tiene un valor de 1. El campo dato CONT es usado para contar el numero de veces que el procedimiento es invocado.

; repeat

; call READ_CARACTER ; until CF=1

; cont = numero de invocaciones CLC MOV CONT,0 L1: CALL READ_CARACTER INC CONT JNC L1

13.10 Cambiar el signo de un número: NEG.

Una de las operaciones fundamentales que puedes ejecutar en un numero con signo es cambiar su signo de negativo a positivo o viceversa. Para hacer esto usa la instrucción NEG, su formato es:

NEG operando

El operando puede ser un campo dato o un registro: Aquí hay algunos ejemplos:

NEG AX

NEG VALOR

(29)

NEG CL

13.11 Multiplicación: MUL y IMUL.

Existen dos instrucciones para multiplicar: MUL para números sin signo e IMUL para números con signo. Excepto por la diferencia entre los tipos, el comportamiento de las instrucciones es el mismo. El formato es

MUL operando IMUL operando

El operando puede ser un registro o un campo dato, y puede ser de tipo byte o word. Para multiplicar el procesador necesita dos operandos, si especificas un operando byte, el procesador asume que el otro operando está en AL, si especificas un operando word, el procesador asume que el otro operando está en AX.

Por ejemplo, si BVALOR es un campo dato de tipo byte, la siguiente instrucción multiplica AL por VALOR:

MUL BVALOR

si WVALOR es un campo dato de tipo word, la siguiente instrucción multiplica AX por WVALOR MUL WVALOR

Cuando multiplicas, el resultado puede ser el doble de grande que los operandos. Cuando multiplicas un byte por otro, el procesador siempre genera un resultado de 2 bytes, la parte izquierda está en AH, y la derecha en AL.

Aquí hay un ejemplo. Digamos que AL tiene un valor de 100 (64H) y BVALOR contiene 10 (0AH). antes de la multiplicación, AX contiene

AH AL

?? 64

Después de la multiplicación, el resultado es 1000 (3E8H) y AX contiene

AH AL

03 E8

Cuando multiplicas una palabra por otra el procesador siempre genera un resultado de 4 bytes. La parte izquierda queda en DX y la derecha en AX. Aquí hay un ejemplo. Digamos que WVALOR contiene 1000 (3E8H) y AX contiene 55555 (D903H). Antes de la multiplicación DX:AX contiene

DX AX

???? D903

Después de la multiplicación , el resultado es 55555000 (34FB3B8H) y DX:AX contiene

DX AX

(30)

Muchas de las veces el resultado es suficientemente pequeño para ocupar el operando original. De hecho, cuando multiplicas un byte por otro, si el resultado cabe en AL puedes ignorar AH. Similarmente cuando multiplicas palabras, si el resultado cabe en AX se puede ignorar a DX.

Para ayudarte a determinar cuando puedes ignorar la parte izquierda del resultado, MUL e IMUL activan las bandera CF y OF. Si el resultado es bastante pequeño para ocupar a AL (bytes) o AX (words), CF y OF están puestas a 0. Si el resultado es más grande, CF y OF están a 1.

Aquí hay un ejemplo que multiplica CX por, AX usando números sin signo,. Después de la multiplicación, AX se copia a RESULT1. Si el resultado está extendido hasta DX, DX se copia a RESULT2. MUL CX MOV RESULT1,AX JNC L1 MOV RESULT1,DX L1:

13.12 Usar IMUL para multiplicar por un valor inmediato.

Puedes usar una variación de la instrucción IMUL para multiplicar por un valor inmediato. Esta variación trabaja con valores con signo y sin signo. En formato es:

IMUL operando, valor-inmediato

IMUL destino,operando,valor-inmediato El operando y destino pueden ser cualquiera de estos registros: AX BX CX DX SP BP SI DI

El procesador multiplica el operando por el valor inmediato. Si un destino es especificado, el resultado es copiado a destino, sino, el resultado reemplaza al operando.

Aquí hay algunos ejemplos:

IMUL AX,10 IMUL SI,BX,-5

El primer ejemplo multiplica AX por 10. El segundo multiplica BX por -5 y el resultado se copia en SI. BX permanece sin cambios.

Cuando usas está variación de IMUL, el resultado siempre es colocado en un registro de una palabra. Si el resultado cabe en una palabra, IMUL limpia las banderas de acarreo y de sobreflujo (CF y OF) en 0. Si el resultado no cabe, IMUL pone las banderas CF y OF a 1.

Sin embargo está variación solo retiene los 16 bits de más a la derecha del resultado, así siempre cabra el resultado en la palabra.

(31)

El modo más fácil de multiplicar un byte por una palabra es copiar el byte a AL y entonces ajustar AH al valor apropiado, si estas usando números sin signo, por 0´s en AH. Si estas usando números con signo, usa la instrucción CBW.

Aquí hay dos ejemplos. El primero usa MUL para multiplicar dos campos dato sin signo: UBYTE es de tipo byte u UWORD es de tipo palabra.

; multiplicar ubyte por uword

MOV AL,UBYTE MOV AH,0 MUL UWORD

El segundo ejemplo usa IMUL para multiplicar dos números con signo: SBYTE es de tipo byte y SWORD es de tipo word.

; multiplicar sbyte por sword

MOV AL,BYTE CBW

IMUL SWORD

13.14 División: DIV y IDIV.

Existen dos instrucciones para dividir: DIV para números sin signo e IDIV para números con signo. Estas instrucciones tiene el siguiente formato:

DIV operando

IDIV operando

Puedes dividir una palabra entre un byte o una palabra doble entre una palabra. Existen dos resultados el cociente y el residuo, ambos son números enteros.

La instrucción IDIV está diseñada para que el signo del residuo sea el mismo del cociente. Por ejemplo, si divides -53 entre 7 el resultado es: un cociente de -7 y un residuo de -4.

El operando que especificas es el divisor. El dividendo puede ser el contenido del registro AX o DS:AX. Si especificas un operando de tipo byte, DIV e IDIV asumen que AX contiene el dividendo de una palabra. El cociente es colocado en AL y el residuo en AH. Si especificas un operando de tipo palabra, DIV e IDIV asumen que DX:AX contiene el dividendo de la doble palabra. El cociente es colocado en AX y el residuo en DX.

Aquí hay dos ejemplos. El primer ejemplo divide 53 entre BVALOR. BVALOR en un campo dato de tipo byte con un valor de 10. Primero copio 53 a AX. Entonces usa DIV para ejecutar la división.

; dividir 53 / BVALOR

MOV AX,53

DIV BVALOR

(32)

El segundo ejemplo usa números con signo y divide DVALOR entre WVALOR. DVALOR es de tipo palabra doble y tiene un valor de -500003. WVALOR es de tipo word con un valor de 100. Primero copio DVALOR a DX:AX. Entonces usa la instrucción IDIV para hacer la operación.

Para copiar una palabra doble a DX:AX, debes usar dos instrucciones mov con el operador PTR, solo recuerda que los datos están al revés así que:

; dividir DVALOR / WVALOR

MOV DX,WORD PTR DVALOR+2

MOV AX,WORD PTR DVALOR

IDIV WVALOR

El resultado es AX con un valor de -5000 y DX con un valor de -3.

Durante una división, una condición de sobreflujo ocurre cuando el resultado es muy grande para ser colocado en el registro AL o AX. La causa más común es la división entre cero. Así debemos evitar cualquier posible división entre cero:

; división que prueba la división entre cero

CMP BVALOR,0

JE L1

MOV AX,WVALOR

DIV BVALOR

L1:

13.15 Dividir un byte entre un byte o una palabra entre una palabra.

Las instrucciones DIV e IDIV solo pueden dividir una palabra entre un byte o una palabra doble entre una palabra. Si quieres dividir un byte entre un byte o una palabra entre una palabra, debes extender el dividendo a la longitud correcta.

La forma más fácil es copiar el dividendo a AL o AX y entonces ajustar AH o DX dependiendo si el numero tiene signo o no. A continuación hay cuatro ejemplos que ilustran las posibilidades. El primer ejemplo usa DIV para dividir un byte sin signo entre otro. Los campos dato UBYTE1 y UBYTE2 son números sin signo de tipo byte.

; dividir UBYTE1 / UBYTE2

MOV AL,UBYTE1 MOV AH,0

DIV UBYTE2

El segundo ejemplo usa IDIV para dividir un numero con signo entre otro los dos de tipo byte. ; dividir SBYTE1 / SBYTE2

MOV AL,SBYTE1 CBW

IDIV SBYTE2

(33)

; dividir SBYTE1 / SBYTE2

MOV AX,UWORD1 MOV DX,0

DIV UWORD2

El cuarto ejemplo usa IDIV para dividir dos números con signo de tipo word. ; dividir SWORD1 / SWORD2

MOV AX,SWORD1 CWD

DIV SWORD2

(34)

CAPITULO 14 ARITMETICA DECIMAL

En este capítulo aprenderás a usar las instrucciones para ejecutar aritmética básica en decimal. Nuevos Términos

Número decimal. Un número en el cual los dígitos decimales son almacenados como entidades separadas.

Número decimal sin empacar. Un numero decimal en el cual sus dígitos están almacenados uno por byte.

Numero decimal empacado. Un numero decimal en el cual cada dos dígitos están almacenados en un byte.

Numero decimal codificado en binario. abreviado BCD; representación binaria del código decimal.

14.1 Números decimales.

Como sabes, los números con signo y sin signo son almacenados en binario, los cuales son representados como valores hexadecimales. Los números decimales son almacenados como entidades separadas de dígitos. Existen dos maneras de hacer esto. Si cada dígito decimal ocupa un byte el numero es llamado decimal sin empacar, si cada dos dígitos decimales están representados por un byte se dice que el numero es decimal empacado.

La figura 14-1 contiene un ejemplo que muestra el numero 54321 (D431H) almacenado como numero sin signo, numero decimal sin empacar y numero decimal empacado.

Figura 14-1 La maneras de almacenar un decimal 54321 Sin signo

hex: D 4 3 1

binario: 1101 0100 0011 0001

Decimal sin empacar

hex: 0 5 0 4 0 3 0 2 0 1

binario: 0000 0101 0000 0100 0000 0011 0000 0010 0000 0001 Decimal empacado

hex: 0 5 4 3 2 1

binario: 0000 0101 0100 0011 0010 0001

14.2 Cuando usar números decimales.

Existen dos condiciones bajo las cuales es preferible usar aritmética decimal en lugar de binaria. Primero, hay algunas veces cuando los números que usas representan valores estrictamente decimales. El ejemplo más común es ejecutar cálculos financieros con pesos (dólares) y centavos. Si se manejaran en base dos las conversiones provocarían errores de calculo intolerables hablando de números enteros. La segunda condición es cuando los números deben ser leídos o escritos, antes de que los números sean escritos, estos deben ser convertidos a caracteres. De hecho no puedes escribir el numero 54321; en su

(35)

lugar debes escribir los caracteres “5”, ”4”, ”3”, ”2”, ”1”. Similarmente solo los caracteres y no los números pueden ser leídos.

Como explique en el capitulo 2, cada carácter está representado por una cadena de 8 bits del código ASCII. Debido a que solo hay 10 dígitos decimales, puedes escribir a leer números usando los diez códigos ASCII, vea la tabla 14-1.

Tabla 14-1 Código ASCII de los diez dígitos Carácter Hex Binario

“0” 30 0011 0000 “1” 31 0011 0001 “2” 32 0011 0010 “3” 33 0011 0011 “4” 34 0011 0100 “5” 35 0011 0101 “6” 36 0011 0110 “7” 37 0011 0111 “8” 38 0011 1000 “9” 39 0011 1001

Es fácil ver que la conversión entre ASCII y decimal empacado no podría ser más simple. Así cuando lees y escribes números, es más fácil trabajar con aritmética decimal para facilitar las conversiones a ASCII. Muchas de las veces es aun más fácil utilizar decimal empacado, ya que ocupan menos espacio y se ejecutan más rápido

Sin embargo el procesador no puede ejecutar aritmética decimal directamente, debes usar las operaciones estándar ADD, SUB, DIV y MUL, y el procesador provee instrucciones especiales para ajustar valores de decimal a binario.

Las instrucciones de ajuste siempre operan en el contenido del registro AL. Así cuando uses aritmética decimal, el primer operando debe estar en AL.

La aritmetica decimal está limitada a dos pasos importantes: primero, debes trabajar solo con un byte a la vez. Así si estas usando números decimales sin empacar, debes procesar un dígito a la vez. Si estas usando números decimales empacados, puedes procesarlo dos dígitos a la vez.

Segundo, los números decimales empacados solo pueden ser sumados y restados. Si quieres dividir y multiplicar, debes convertir estos números a sus equivalentes sin empacar.

14.3 Adición decimal: AAA y DAA.

Para sumar números decimales un byte a la vez, copia uno de los operandos a AL, suma con la instrucción ADD, y ajusta el resultado para convertirloa un numero decimal valido.

Si estas sumando números sin empacar usa la instrucción AAA para hacer el ajuste, si usas números sin empacar usa la instrucción DAA

Aquí hay dos ejemplos de sumas usando campos dato de tipo byte. U1 y U2 son números sin empacar; P1 y P2 son números empacados:

(36)

U1 DB 7H

U2 DB 6H

P1 DB 95H P2 DB 07H

Note que cada byte sin empacar ocupa un dígito; cada byte empacado ocupa dos dígitos.

El primer ejemplo de abajo muestra la suma de dos números sin empacar. El segundo ejemplo muestra como sumar dos números empacados.

; suma de dos números sin empacar MOV AL,U1 ADD AL,U2 AAA

; suma de dos números empacados MOV AL,P1 ADD AL,P2 DAA

Hay algunas veces que el resultado de la suma no cabe en AL, en los números sin empacar esto ocurre cuando la suma es mayor de 9. Con los números empacados, esto pasa cuando la suma es mayor que 99. En la siguiente sección veremos como sumar números decimales multibytes.

14.4 Sumar números multibytes decimales sin empacar.

Para sumar dos números decimales sin empacar multibytes, debemos sumar un byte a la vez de derecha a izquierda. Después de cada suma debemos hacer el ajuste respectivo. Para todas menos la primer suma debemos tomar en cuanta en contenido de CF.

Esta sección contiene un ejemplo que suma dos números decimales sin empacar de tres bytes U1 y U2, el resultado queda en U1. U1 y U2 están definido como sigue

U1 DB 0H,5H,2H,1H U2 DB 0H,4H,8H,4H

El ejemplo suma U2 y U1 un byte a la vez, de derecha a izquierda. Cada adición involucra cuatro pasos. 1.- Copiar un byte de U1 a AL.

2.- Sumar un byte de U2 a AL. 3.- Ajustar el resultado.

4.- Copiar el resultado ajustado a AL.

La primera adición usa ADD, las subsecuentes adiciones usan ADC para contar el posible acarreo de byte a byte.

Es importante recordar que puede haber un acarreo hasta en la ultima suma. Estamos sumando dos números de tres dígitos y obtenemos un numero de cuatro (521 + 484 = 1005). Una forma de resolver esto es con un dígito extra (el primer cero) donde se copiara el acarreo extremo si lo hay.

(37)

Como sabemos si se produce ese dígito extra. Si el ajuste produce un resultado que es muy grande para AL, la instrucción AAA hace dos cosas

pone CF a 1 suma 1 a AH

Así, si pones un cero en Ah en el ultimo ajuste, AAA dejará Ah con un valor de cero o 1. Todo lo que tienes que hacer es esperar hasta el ultimo ajuste y entonces copiar AH al dígito extra del resultado. ; suma de dos dígitos multibytes sin empacar: U1<--U1+U2

; --- suma U2+3 a U1+3

MOV AL,U1+3 ADD AL,U2+3 AAA

MOV U1+3,AL ; --- suma U2+2 a U1+2

MOV AL,U1+2

ADC AL,U2+2

AAA

MOV U1+2.AL ; --- suma U2+1 a U1+1

MOV AL,U1+1

ADC AL,U2+1

MOV AH,0 AAA

MOV U1+1,AL

; --- ajustar el dígito de mas a la izquierda de U1 MOV U1,AH

14.5 Sumar números multibytes decimales empacados.

Hay un par de diferencias entre las técnicas de suma multibyte de números decimales sin empacar y empacados.

Primero, debes usar un ajuste diferente a AAA, DAA. Segundo, debes procesar dos dígitos a la vez (debido a que cada byte contiene dos dígitos). Tercero, debes depender de la bandera de acarreo para saber si hay un acarreo extremo en la ultima suma.

El siguiente ejemplo, ilustra como sumar dos números empacados de tres bytes, P1 y P2, el resultado queda en P1.

P1 DB 00H,56H,43H,21H P2 DB 00H,45H,68H,54H

Esta operación es 564321 + 456854 = 1021175.

; suma de dos dígitos multibytes empacados: P1<--P1+P2 ; --- suma P2+3 a P1+3

MOV AL,P1+3 ADD AL,P2+3 DAA

(38)

MOV P1+3,AL ; --- suma P2+2 a P1+2 MOV AL,P1+2 ADC AL,P2+2 DAA MOV P1+2.AL ; --- suma P2+1 a P1+1 MOV AL,P1+1 ADC AL,P2+1 DAA MOV P1+1,AL

; --- if CF=1 pon el dígito de mas a la izq. de P1 a 1 ; --- else pon el dígito de mas a la izq. de P1 a 0

JC L1 JNC L2 L1: MOV P1,1 JMP L3 L2: MOV P1,0 L3:

14.6 Substracción decimal: AAS y DAS.

La substracción decimal es similar a la suma decimal. Procesas los números byte por byte, de derecha a izquierda. El numero desde el cual estas substrayendo es copiado a AL.

Como en la suma decimal, debe ajustar después de cada resta. Si estas restando decimales sin empacar, usa la instrucción AAS. Si estas restando decimales empacados usa DAS, estos asumen que el resultado del calculo previo está en AL:

Aquí hay dos ejemplos. El ejemplo usa bytes, U1 y U2 son números sin empacar, P1 y P2 son números empacados:

U1 DB 7H

U2 DB 6H

P1 DB 95H P2 DB 07H

El primer ejemplo resta dos números sin empacar ; resta dos números sin empacar AL<--U1-U2 MOV AL,U1

SUB AL,U2

AAS

El segundo ejemplo resta dos números empacados ; resta dos números empacados AL<--P1-P2 MOV AL,P1

(39)

SUB AL,P2 DAS

Si el numero que estas restando es mayor que el numero en AL, AAS y DAS ponen la bandera de acarreo en 1 para indicar el préstamo. AAS también puede restar 1 de AH. Si no hay préstamo, AAS y DAS ponen CF a 0.

14.7 Restando números decimales multibytes sin empacar.

Para restar dos números de este tipo, debes restar los bytes uno a la vez, de derecha a izquierda. Después de cada resta, debes hacer el ajuste. En la ultima resta debes tomar en cuenta el valor de la bandera de acarreo CF.

Esta sección contiene un ejemplo de resta de dos números de tres bytes sin empacar, U1 y U” de tipo byte U1 DB 5H,2H,1H

U2 DB 4H,8H,4H

El ejemplo resta U de U2, un byte a la vez con un proceso de cuatro pasos: 1.- Copia un byte de U1 a AL.

2.- Substrae un byte de U2 desde AL. 3.- Ajusta el resultado.

4.- Copia el resultado a U1.

La primera resta usa SUB, las restas subsecuentes usan SBB para tomar en cuenta el posible préstamo. Si hay un préstamo extremo se debe saltar a una condición de error que maneje el problema adecuadamente. ; resta de números decimales sin empacar: U1 <-- U1-U2

MOV AL,U1+2

SUB AL,U2+2

AAS

MOV U1+2,AL ; --- resta U2+1 de U1+1

MOV AL,U1+1 SBB AL,U2+1 AAS MOV U1+1,AL ; --- resta U2 de U1 MOV AL,U1 SBB AL,U2 AAS MOV U1,AL

; si la ultima resta genero un prestamo (CF=1) ; salta a una condición de error

(40)

14.8 Restando números decimales multibytes empacados.

Hay un par de diferencias entre las técnicas de resta multibyte de números decimales sin empacar y empacados.

Primero, debes usar un ajuste diferente a AAS, DAS. Segundo, debes procesar dos dígitos a la vez (debido a que cada byte contiene dos dígitos).

El siguiente ejemplo, ilustra como restar dos números empacados de tres bytes, P1 y P2, el resultado queda en P1.

P1 DB 56H,43H,21H P2 DB 45H,68H,54H

; resta de números decimales empacados: P1 <-- P1-P2 MOV AL,P1+2 SUB AL,P2+2 DAS MOV P1+2,AL ; --- resta P2+1 de P1+1 MOV AL,P1+1 SBB AL,P2+1 DAS MOV P1+1,AL ; --- resta P2 de P1 MOV AL,P1 SBB AL,P2 DAS MOV P1,AL

; si la ultima resta genero un prestamo (CF=1) ; salta a una condición de error

JC ERROR_RESTA

14.9 Multiplicación decimal.

Debes multiplicar números decimales un byte a la vez. Copia uno de los operandos a AL. Después multiplícalo por otro operando usando la instrucción MUL, y después lleva a cabo el ajuste.

EL procesador solo puede multiplicar números decimales sin empacar. Así la única instrucción de ajuste es AAM, si quieres multiplicar números empacados tendrás de desempacarlos primero.

Ya que cada operando tiene un rango de 0 a 9, el resultado está en el rango de 0 a 81. El primer dígito es colocado en AH; el segundo dígito es colocado en AL. Si el resultado es 9 o menor, AH contendrá un 0. No existe sobreflujo posible para el resultado.

Aquí hay un ejemplo que multiplica dos campos dato, U1 y U2, cada uno contiene un dígito decimal sin empacar.

Referencias

Documento similar

Cedulario se inicia a mediados del siglo XVIL, por sus propias cédulas puede advertirse que no estaba totalmente conquistada la Nueva Gali- cia, ya que a fines del siglo xvn y en

De acuerdo con Harold Bloom en The Anxiety of Influence (1973), el Libro de buen amor reescribe (y modifica) el Pamphihis, pero el Pamphilus era también una reescritura y

En junio de 1980, el Departamento de Literatura Española de la Universi- dad de Sevilla, tras consultar con diversos estudiosos del poeta, decidió propo- ner al Claustro de la

Volviendo a la jurisprudencia del Tribunal de Justicia, conviene recor- dar que, con el tiempo, este órgano se vio en la necesidad de determinar si los actos de los Estados

Primeros ecos de la Revolución griega en España: Alberto Lista y el filohelenismo liberal conservador español 369 Dimitris Miguel Morfakidis Motos.. Palabras de clausura

b) El Tribunal Constitucional se encuadra dentro de una organiza- ción jurídico constitucional que asume la supremacía de los dere- chos fundamentales y que reconoce la separación

De este modo se constituye un espacio ontológico y epistemológico a la vez, en el que cada elemento (cada principio) ocupa un lugar determinado en la totalidad, y desde ahí está