ASM POR AESOFT. (lección 8). -------------------------------------------------------------------- - DUDAS DE LECCIONES ANTERIORES - SOLUCION AL EJERCICIO DE LA LECCION ANTERIOR - CONJUNTO DE INSTRUCCIONES DEL 8086(II): * Operaciones lógicas ó booleanas (Continuación del apartado Operaciones de manejo de bits, lección 7). * Operaciones de manejo de hileras o cadenas de caracteres. -------------------------------------------------------------------- Saludos, mis queridos programadores. :-)) En la lección de hoy, vamos a seguir con la relación de las instrucciones del 8086. También veremos en primer lugar la respuesta que le doy a un usuario acerca de unas dudas que me plantea. Dicho mensaje me parece de interés general, por eso lo incluyo en la lección de hoy. Por último, aunque no en último lugar, daré la solución al sencillo ejercicio que os propuse en la lección anterior, ya que parece que nadie me ha dicho cómo resolverlo. - DUDAS DE LECCIONES ANTERIORES ------------------------------- A continuación os muestro un mensaje que considero de interés para todas las personas que siguen el curso: --- ----------------------------------- inicio del mensaje. > Cada uno de estos registros tiene funciones > especiales que es interesante > conocer. Por ejemplo el registro AX es el > llamado acumulador, hace que > muchas operaciones tengan una forma más corta, > ya que lo especifican > implícitamente. Es decir, que hay operaciones > que actúan sobre el > registro AX en particular. AJ> Con esto te refieres por ejemplo a cuando se llama a una AJ> interrupcion con un valor en AX sin que tengamos que indicarle para AJ> nada donde tiene que encontrar ese valor, puesto que ya sabe que lo va AJ> a encontrar en AX :-? Me refería más bien a ciertas instrucciones aritméticas, que presuponen que un operando se encuentra almacenado en AX, y el otro operando puede estar en cualquiera de los otros registros. Cuando leas la lección 7 (que debes tener ya en tus manos) comprenderás esto mejor. El registro AX (como acumulador que es), se utiliza en otras muchas instrucciones de forma implícita (esto es, que no es necesario indicarlo expresamente). Entre estas otras instrucciones podemos encontrar ciertas instrucciones de transferencia de cadenas de caracteres: LODS y STOS, que utilizan el registro AL (en el caso de las instrucción LODSB y STOSB) ó el registro AX (en el caso de las instrucción LODSW y STOSW). La diferencia entre estas instrucciones estriba en el hecho de trabajar con bytes (registros de 8 bits) ó trabajar con palabras (registros de 16 bits). Pero bueno, no me enrollo aquí más, ya que estas instrucciones se desarrollan en la lección 8. En cuanto a lo que me dices acerca de las interrupciones, no sería el ejemplo más acertado, pero es válido, ya que cuando llamas a una interrupción (DOS, BIOS, etc.), ésta sabe qué función de la interrupción ejecutar gracias al registro AX, que contiene el número de dicha función. Entonces podemos decir que la instrucción INT (llamada a interrupción), utiliza el registro AX implícitamente al no aludir en la sintaxis de dicha instrucción a tal registro, y utilizar la instrucción dicho registro para conocer el número de función que ejecutar. Obviamente antes de la instrucción INT, hemos tenido que cargar en AX (mediante la instrucción MOV, por ejemplo) el valor adecuado (número de la función a ejecutar). > Af: Bit de carry auxiliar, se activa si una > operación aritmética produce > acarreo de peso 16. AJ> ¿que es eso de acarreo de peso 16? El peso (al hablar de los bits de un registro) es la posición que ocupa un bit determinado dentro de un registro. Por decirlo de alguna manera, es la importancia de ese bit dentro del registro. Al igual que ocurre en la base decimal, en la que el dígito de más a la derecha de un número es el menos importante (de menor peso), así ocurre en la base binaria (bits) y en el resto de las bases, por supuesto. Entonces cuando hablamos de acarreo de peso 16, nos referimos al acarreo que surge fruto de trabajar con los bits de mayor peso (peso 16), los de más a la izquierda. --- Una cosa que no dije en su momento: El flag Af (bit de carry auxiliar) se utiliza en operaciones decimales. No debe tenerse en cuenta en operaciones aritméticas con enteros. De cualquier modo, no es necesario prestarle demasiada atención a este flag. Cuando uno empieza, nunca lo utiliza. Y cuando ya lleva mucho tiempo programando, tiene experiencia y hace cosas muy técnicas que requieran de dicho flag... Entonces, evidentemente ya sabreis todo lo necesario acerca de él. :-))) O sea, que pasando de él. > Venga, ahora quiero que me conteis dudas que > teneis, aclaraciones, etc. AJ> Como ves te he hecho caso y aqui tienes un par de dudillas ;-) Pues ahí queda mi respuesta. Espero haberte ayudado. --- ---------------------- fin del mensaje Espero que os haya parecido interesante. - SOLUCION AL EJERCICIO DE LA LECCION ANTERIOR ---------------------------------------------- Esto es lo que os proponía en la lección anterior: --- A ver si alguien me dice cómo podemos modificar el flag Tf, por ejemplo. Os daré una pista: ¿Recordais las instrucciones PUSHF y POPF? Espero vuestros mensajes. Si a nadie se le ocurre, ya dejaré yo la solución en una próxima lección. --- Y aquí está la solución: Debido a que no podemos acceder directamente a determinados bits del registro de estado (FLAGS), debemos realizar una serie de operaciones para que de esta forma nos sea posible la modificación de los bits a los que no podemos acceder directamente. Si lo que queremos es poner el flag Tf con valor (1), entonces basta con realizar la siguiente operación: MOV AX,0000000100000000b PUSH AX POPF La primera instrucción prepara un nuevo registro de estado (FLAGS) en el que como sólo nos interesa el flag Tf, lo ponemos a 1 (que es lo que queremos), y los otros bits (flags) los dejamos a Cero, los podriamos haber dejado a 1 también. Para el caso que nos ocupa, da igual. La segunda instrucción deja este nuevo registro de estado en la pila. Esto se hace así, ya que tenemos una instrucción que sacará ese nuevo registro de estado de la pila, y lo pondrá como nuevo registro de estado o FLAGS. La tercera línea hace que el nuevo registro de estado sea la palabra introducida en la pila por la orden (PUSH AX). Es decir, se trata de utilizar la instrucción POPF para poder coger de la pila un nuevo registro de estado a gusto del programador, que previamente ha sido depositado en la pila (mediante PUSH registro). Si queremos poner el flag Tf con valor (0), basta con: MOV AX,0 PUSH AX POPF Con lo expuesto hasta ahora se resolvía el ejercicio que os pedí, pero esto en la práctica no es nada útil, ya que estamos 'machacando' el valor del resto de los flags, cuando sólo queremos modificar uno en concreto. para evitar eso, nos valemos de las operaciones lógicas que vamos a ver a continuación. Estas operaciones lógicas las utilizaremos (en este caso) para aislar el resto de los bits, y así mantener su valor original. ~~~ Una vez que hayais estudiado las operaciones lógicas que se desarrollan más abajo, estareis en condiciones de solucionar el siguiente ejercicio: ... Se trata de modificar el flag Tf, pero (y esto es muy importante) sin cambiar el valor del resto de flags. Espero que alguien me dé la solución (si lo haceis todos, mejor) :-)). - CONJUNTO DE INSTRUCCIONES DEL 8086 (II) ----------------------------------------- Continuamos en este apartado con la relación y explicación de las instrucciones del 8086: * Operaciones lógicas ó booleanas * (Continuación de Operaciones de manejo de bits). Todos habreis visto que en las buenas calculadoras (os recuerdo que la mejor es SB-CALCU del programa SANBIT :-)) aparecen una serie de operaciones como son: NOT, AND, OR, etc... Pues bien, esas son las llamadas operaciones lógicas. En serio, os recomiendo que utiliceis el programa SANBIT para realizar todo tipo de operaciones booleanas (Un poco de publicidad :-))) Estas operaciones trabajan a nivel de bits, con un tamaño de operando dado. Esto es, no es lo mismo un NOT (7) con un tipo de datos byte, que originaría como resultado 248, que hacer un NOT (7) con un tipo de datos word, que originaría como resultado 65528. Vemos pues, que el tipo de datos sobre el que se realiza una operación lógica, condiciona el resultado de la operación. La finalidad de estas instrucciones es modificar uno o varios bits concretos de un registro o una posición de memoria. Son útiles para aislar ciertos bits, y trabajar con ellos como si fueran variables independientes. Es decir, usando adecuadamente estas operaciones lógicas, podemos tener hasta 16 variables de tipo lógico (valor 0 ó 1) en un registro de tipo word. Como veremos a continuación, estas operaciones se utilizan muchas veces para realizar con mayor rapidez y menor código de ejecución ciertas acciones, que por métodos más comunes acarrearían más tiempo y código. - NOT lógico. La operación lógica NOT, consiste en sustituir los unos por ceros y viceversa en un operando dado. Sintaxis: NOT registro/posición_de_memoria. Ejemplo: NOT AL. Supongamos que AL tiene el valor 99h. En binario tendríamos: AL = 10011001. La instrucción lo que hace es invertir el valor de cada bit. Si antes de la instrucción valía 1, ahora valdrá 0, y viceversa. Siguiendo este criterio, después de realizar la operación, AL valdrá 01100110, que en base 16 (hexadecimal) es: AL = 66H. Si ejecutamos de nuevo la instrucción NOT AL, con el nuevo valor de AL, parece evidente lo que vamos a obtener, ¿no? Por supuesto obtendremos el mismo valor que al principio: AL = 99h. Ejemplo: Veamos ahora qué sucede con la operación NOT AX, suponiendo que AX = 99H. Ahora estamos trbajando sobre un operando de tamaño word ó palabra, por lo tanto, tenemos 16 bits a los que cambiar su valor. Antes de la instrucción, AX tenía el valor 0000000010011001. Después de la instrucción, AX = 1111111101100110. En base 16, AX = 0FF66H. --- El primer 0 de 0FF66H se pone para que el ensamblador sepa que estamos --- refiriéndonos a un número, y no a una variable llamada FF66H. - AND lógico. La operación lógica AND, al igual que las restantes -y al contrario que la operación NOT- opera sobre dos operandos. El resultado de la operación se almacena en el primer operando, que puede ser un registro o una posición de memoria. Este resultado se obtiene de la siguiente manera: Se compara cada uno de los bits del primer operando con sus correspondientes bits del segundo operando. Si ambos tienen el valor (1), el bit correspondiente del operando resultado se pone a valor (1). Por el contrario, si alguno de los dos bits (o los dos bits) tiene valor (0), el bit correspondiente del operando resultado valdrá (0). De ahí viene el nombre de la instrucción: AND ... Uno y (AND) otro, los dos bits deben tener valor (1) para que el bit correspondiente del resultado tenga valor (1). Esta operación se utiliza para poner a (0) determinados bits. Sintaxis: AND registro,registro AND registro,posición_de_memoria AND registro,valor_inmediato AND posición_de_memoria,registro AND posición_de_memoria,valor_inmediato Ejemplo: Supongamos que AX = 1717H y VAR1 (Variable con la que accedemos a una posición de memoria de tipo Word) tiene el valor 9876H. En binario tendríamos: AX = 0001011100010111 VAR1 = 1001100001110110 Veamos cómo se realizaría la operación lógica (AND AX,VAR1)... Se trata de operandos de tipo word (16 bits), por tanto hay que realizar 16 operaciones (1 para cada posición de bit) con los bits. Ya hemos visto más arriba la forma en que opera esta instrucción. Veamos qué resultado nos daría: AX = 0001011100010111 VAR1 = 1001100001110110 ----------------------- Tras la instrucción: AX = 0001000000010110, que pasado a base 16 nos queda AX = 1016h. - OR lógico. La operación lógica OR se utiliza al contrario que la operación AND, para poner a (1) determinados bits de un registro o posición de memoria. La operación lógica OR, opera sobre dos operandos, almacenando el resultado de dicha operación en el primer operando, que puede ser un registro o una posición de memoria. Este resultado se obtiene de la siguiente manera: Se compara cada uno de los bits del primer operando con sus correspondientes bits del segundo operando. Si alguno tiene el valor (1), el bit correspondiente del operando resultado se pone a valor (1). Por el contrario, si los dos bits tiene valor (0), el bit correspondiente del operando resultado valdrá (0). De ahí viene el nombre de la instrucción: OR ... Uno u (OR) otro, con que uno sólo de los dos bits tenga valor (1), el bit correspondiente del resultado tendrá valor (1). Sintaxis: OR registro,registro OR registro,posición_de_memoria OR registro,valor_inmediato OR posición_de_memoria,registro OR posición_de_memoria,valor_inmediato Ejemplo: Supongamos que CL = 25H y COLUM (Variable con la que accedemos a una posición de memoria de tipo Byte) tiene el valor 0AEH. En binario tendríamos: COLUM = 10101110 CL = 00100101 Veamos cómo se realizaría la operación lógica (OR COLUM,CL)... Se trata de operandos de tipo byte (8 bits), por tanto hay que realizar 8 operaciones (1 para cada posición de bit) con los bits. Veamos qué resultado nos daría: COLUM = 10101110 CL = 00100101 ----------------------- Tras la instrucción: COLUM = 10101111, que pasado a base 16, nos queda la variable COLUM = 0AFH. - XOR (OR Exclusivo). La instrucción XOR opera en modo parecido a OR, pero con una diferencia muy importante, que le hace tener el sobrenombre de OR Exclusivo: Se compara cada uno de los bits del primer operando con sus correspondientes bits del segundo operando. Si uno y sólo uno de ambos bits comparados tiene valor (1) -obviamente el otro bit debe ser (0), es decir, ambos bits tienen diferente valor-, entonces el bit correspondiente del resultado tendrá valor (0) He aquí la diferencia con la instrucción OR: Mientras que la operación OR admitía que uno o los dos bits fuera (1) para poner a (1) el bit resultante, la instrucción XOR exige que sólo uno de esos bits tenga valor (1), es decir, que ambos bits tengan diferente valor. Sintaxis: XOR registro,registro XOR registro,posición_de_memoria XOR registro,valor_inmediato XOR posición_de_memoria,registro XOR posición_de_memoria,valor_inmediato Esta instrucción se utiliza normalmente para poner a Cero un registro. Es la manera más rápida de poner a cero un registro. Veámoslo con un ejemplo: Supongamos que queremos poner a cero el registro AX. Da igual el valor que tenga AX para obtener el resultado final de la siguiente operación, pero le damos por ejemplo el valor AX = 2637h. En binario tendríamos: AX = 0010011000110111 Si realizamos la operación (XOR AX,AX), cuál podría ser el resultado a almacenar en AX? Parece evidente, no? Hemos quedado en que el bit resultante tiene valor (1) si los dos bits comparados son diferentes, entonces llegamos a la conclusión que todos los bits del resultado van a tener valor (0), ya que al comparar un registro consigo mismo, todos y cada uno de los bits de ambos registros (que en realidad es el mismo registro) son iguales de uno a otro registro. Veamos cómo se realizaría la operación lógica (XOR AX,AX)... Se trata de operandos de tipo word (16 bits), por tanto hay que realizar 16 operaciones (1 para cada posición de bit) con los bits. Veamos qué resultado nos daría: AX = 0010011000110111 AX = 0010011000110111 ----------------------- Tras la instrucción: AX = 0000000000000000, ya que todas las parejas de bits comparados tienen el mismo valor. Esta forma de borrar un registro es muy rápida, y gener muy poco código ejecutable. Vamos a comparar este método con el 'tradicional' (MOV AX,0): La instrucción (MOV AX,0) tiene un código ejecutable de 3 bytes, y tarda en realizarse 4 pulsos de reloj. Mientras que la instrucción (XOR AX,AX) tiene un código de 2 bytes, y tarda en realizarse 3 pulsos de reloj. Puede parecer que la diferencia no es muy grande, pero cuando se ejecutan miles o millones de estas instrucciones en un programa, se nota la diferencia. - TEST. Esta operación es similar a AND, pero con una diferencia muy importante: La instrucción TEST no modifica el valor de los operandos, sino sólo el registro de estado (FLAGS). Para realizar la operación utiliza registros internos del procesador, de esta manera, los operandos pasados a la instrucción TEST, no se ven alterados. Esta instrucción se realiza para comprobar el valor de un cierto bit ó ciertos bits dentro de un registro. Sintaxis: TEST registro,registro TEST registro,posición_de_memoria TEST registro,valor_inmediato TEST posición_de_memoria,registro TEST posición_de_memoria,valor_inmediato Veamos un ejemplo: Existen dos posiciones de memoria de tipo byte consecutivas que utiliza la ROM BIOS para mantener y actualizar el estado de ciertas teclas especiales. Para este ejemplo, nos interesa sólo la primera posición 0000:0417H. Es decir, la posición 0417h dentro del segmento 0000h. Esta posición de memoria contiene las siguientes informaciones lógicas en cada uno de sus bits: ¦bit 7 6 5 4 3 2 1 0 ¦ +-------------------------------+ Estado de teclas cuando el bit ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ correspondiente tiene valor (1). ¦ +-------------------------------+ --------------------------------- ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ +----> Tecla Mays. derecha pulsada. ¦ ¦ ¦ ¦ ¦ ¦ ¦ +--------> Tecla Mays. izquierda pulsada. ¦ ¦ ¦ ¦ ¦ ¦ +------------> Tecla Control pulsada. ¦ ¦ ¦ ¦ ¦ +----------------> Tecla Alt pulsada. ¦ ¦ ¦ ¦ +--------------------> Scroll Lock activado. ¦ ¦ ¦ +------------------------> Num Lock activado. ¦ ¦ +----------------------------> Caps Lock (Bloq Mays) activado. ¦ +--------------------------------> Insert activado. ¦ ¦ ¦ Evidentemente, cuando el bit correspondiente a una información lógica ¦ en particular (Tecla ALT, p.e.) tiene valor (0), esto indica todo lo ¦ contrario a lo mostrado arriba. En el caso del bit 3 (tecla ALT), ¦ si estuviera con valor (0), querría decir que no está pulsada en estos ¦ momentos. El bit 7 (bit de mayor peso, o de más a la izquierda) del byte direccionado por esa posición de memoria, contiene información booleana ó lógica (valor verdadero ó falso) acerca del modo de inserción de teclado. Es decir, mediante este bit podremos saber si el modo de inserción está activado o no. Debido a que hay otra serie de variables lógicas ó booleanas dentro de este byte, no podemos realizar una comparación a nivel de byte para conocer el estado de insertar. Esto es, no podemos escribir algo así como: CMP BYTE PTR ES:[DI],10000000b, ya que el resto de bits tienen un valor variable dependiente de ciertas circunstancias (mayúscula izquierda pulsada, tecla ALT pulsada, etc). Debemos entonces usar la instrucción TEST, con la que podemos 'TESTar' ó examinar el valor de un determinado bit concreto. En el caso que nos ocupa (examinar el estado del modo de inserción), debemos examinar el valor del bit 7 de la posición de memoria 0000:0417h. Veamos todo el proceso: ;******** trozo de programa. XOR AX,AX ; MOV ES,AX ; Mediante estas dos instrucciones, hago que la base del ; segmento direccionado mediante el registro ES, se ; encuentre en la posición 0000h (Al principio de la memoria ; del PC). ; Observaciones: ; - No es posible introducir un valor_inmediato en un ; registro de segmento. Es decir, no podemos escribir algo ; como: MOV ES,8394h. El procesador no lo permite. ; Debemos, pues, valernos de otro registro, como AX, DX, ; etc, para introducir el valor deseado en el registro de ; segmento. ; - Utilización de la operación lógica (XOR AX,AX) para poner ; a Cero el registro AX, ganando en rapidez, y menor tamaño ; del programa ejecutable, en comparación con (MOV AX,0). ; - Tenemos el registro ES apuntando ya al segmento adecuado. ; Sigamos: TEST BYTE PTR ES:[0417H],10000000b ; Examinemos esta instrucción tan ; compleja en profundidad... ; - Vemos que su sintaxis es del tipo: ; TEST posición_de_memoria,valor_inmediato. ; - Debemos indicar que vamos a comparar una posición de tipo ; byte, para evitar errores. Eso lo hacemos mediante: ; BYTE PTR. ; - Debemos indicar a continuación el segmento y desplazamiento ; donde se encuentra ese byte al que vamos a acceder: ; ES:[0417H] ; - Utilizamos el número 10000000b como valor_inmediato. De ; esta forma es como vamos a examinar el bit 7 ó de estado ; de insertar. ; Hemos visto que la instrucción TEST es un AND que no ; modifica el valor de los operandos, sino sólo los flags. ; Por tanto, sólo podemos saber el valor de ese bit 7, ; comprobando el valor de los flags tras la operación. ; El flag que hay que 'mirar' es el flag Zf (flag Cero). ; Si tras la operación el flag Zf está con valor (1), esto ; quiere decir que el bit 7 de ES:[0417H] está desactivado, ; es decir, que estamos en estado de NO_INSERCION. ; Veamos gráficamente el proceso de esta instrucción TEST para comprenderlo ; mejor: ; ; Vamos a suponer que el byte ES:[417H] tiene el siguiente valor: 10101110b. ; Es un valor que he puesto al azar para poder operar con algo concreto. ; ; Tenemos entonces los dos valores siguientes para hacer el TEST: ; ; ES:[0417H] = 10101110 ; valor_inmediato = 10000000 ; --------------------------- ; Tras la ejecución, un registro interno del procesador (no accesible por ; el programador) tendrá el siguiente valor: 10000000b ; Y el flag Zf que indica si el resultado tiene un valor Cero, estará puesto ; a (0), ya que el resultado tiene un valor distinto de cero. ; Por tanto, comprobando el valor del flag Zf tras la operación, sabremos ; que insertar está en modo activo. JZ no_insertando ; Si el flag Zf tiene valor (1): ; Hacemos un salto condicional a una posición del ; programa, en la que se realizan las acciones ; pertinentes en el caso de que no esté el teclado en ; modo inserción. [....] ; se realizan las instrucciones adecuadas para el caso ; en que el teclado está en modo de inserción. JMP fin_insercion ; salto el trozo siguiente, reservado para cuando el ; teclado está en modo de NO_INSERCION. no_insertando: ; indica el inicio del código preparado para utilizar ; en caso de NO_INSERCION. [....] ; se realizan las instrucciones adecuadas para el caso ; en que el teclado NO está en modo de inserción. fin_insercion: ; Hemos terminado de trabajar por ahora con el tema de ; la inserción, pasamos a otra cosa dentro del programa. [....] ; sigue el programa. ;****** fin del trozo de programa. Veamos cómo quedaría sin tantas explicaciones: ;********* trozo de programa: XOR AX,AX MOV ES,AX TEST BYTE PTR ES:[0417H],10000000b JZ no_insertando [....] ;acciones para cuando INSERTANDO. JMP fin_insercion no_insertando: [....] ;acciones para cuando NO_INSERTANDO. fin_insercion: [....] ;sigue el programa. ;**********fin del trozo de programa. Supongamos ahora que el byte ES:[417H] tiene el siguiente valor: 00100010b. Con este nuevo valor, el estado de insertar es falso (NO_INSERTAR). Tenemos entonces los dos valores siguientes para hacer el TEST: ES:[0417H] = 00100010 valor_inmediato = 10000000 --------------------------- Tras la ejecución, un registro interno del procesador tendrá el siguiente valor: 00000000b Y el flag Zf que indica si el resultado tiene un valor Cero, estará puesto a (1), ya que el resultado tiene un valor de cero. En este caso, al comprobar el valor del flag Zf, sabremos que insertar está en modo inactivo (es decir, el teclado está en modo SOBREESCRIBIR). - NEG. Esta operación, por su forma de trabajar(equivale a un NOT seguido de un INC) podemos estudiarla aquí, pero la vamos a dejar para cuando tratemos los números negativos, ya que se utiliza para eso, para convertir un número en negativo. NEG AX ¦ (Equivalentes) NOT AX INC AX --- Operaciones para el manejo de hileras o cadenas de caracteres Este tipo de instrucciones nos permiten (a grandes rasgos) realizar movimientos de bloques de memoria de una posición de la memoria a otra. Veamos cada una de ellas: - MOVS (MOV String, mover cadena de caracteres). Se utiliza para mover un byte (MOVSB) o una palabra (MOVSW) desde la posición de memoria direccionada por DS:SI a la dirección ES:DI. Antes de introducir esta instrucción en el programa, debemos haber cargado debidamente los registros con sus valores apropiados. En caso de mover un byte, utilizamos la sintaxis: MOVSB. En caso de mover una palabra, utilizamos la sintaxis: MOVSW. En estos momentos puede parecer de poca utilidad esta instrucción, ya que el mismo resultado lo podemos obtener con la instrucción MOV que vimos en lecciones anteriores. Veremos la utilidad de esta instrucción y las siguientes, cuando veamos las partículas: REP (REPetir), REPZ ó REPE, y REPNZ ó REPNE. MOVSB ---> Mueve el byte direccionado por DS:SI a ES:DI. MOVSW ---> Mueve la palabra direccionada por DS:SI a ES:DI. - LODS (LOaD String, cargar cadena de caracteres en el acumulador). Se utiliza para introducir en el registro acumulador (AX si trabajamos con palabras; AL si trabajamos con bytes) la palabra o byte direccionado mediante DS:SI. LODSB ---> Introduce en el registro AL (tamaño byte) el byte direccionado mediante DS:SI. LODSW ---> Introduce en el registro AX (tamaño palabra ó word) el byte direccionado mediante DS:SI. - STOS (STOre String, almacenar cadena de caracteres). Almacena el contenido del acumulador (AX si trabajamos con palabras; AL si trabajamos con bytes) en la posición de memoria direccionada mediante ES:DI. STOSB ---> Almacena el contenido del registro AL (tamaño byte) en la posición de memoria ES:DI. STOSW ---> Almacena el contenido del registro AX (tamaño palabra) en la posición de memoria ES:DI. - CMPS (CoMPare String, comparar cadenas de caracteres). Se utiliza para comparar cadenas de caracteres. Compara las cadenas que empiezan en las direcciones DS:SI y ES:DI. Podemos comparar un byte (byte a byte, cuando usemos la partícula REP) o una palabra (palabra a palabra, cuando usemos la partícula REP). CMPSB ---> Compara el byte situado en DS:SI con el byte situado en ES:DI. CMPSW ---> Compara la palabra situada en DS:SI con la palabra situada en ES:DI. -SCAS (No_se_qué String :-) Compara el contenido del acumulador (AX si trabajamos con palabras; AL si trabajamos con bytes) con la palabra o byte situado en la posición de memoria ES:DI. SCASB ---> Compara el contenido del registro AL (tamaño byte) con el byte situado en la posición ES:DI. SCASW ---> Compara el contenido del registro AX (tamaño word) con la palabra situada en la posición ES:DI. Bien... Hasta ahora hemos visto que estas instrucciones trabajan sólo con un byte o palabra. Son raras las ocasiones en las que utilizamos estas instrucciones para mover sólo un byte o palabra. Lo más normal es utilizar estas instrucciones antecedidas de una de las siguientes partículas de repetición: + REP (REPetir CX veces) Repite una de las operaciones de movimiento/comparación de cadenas tantas veces como indique el registro CX. Tras cada una de estas repeticiones se decrementa el valor del registro CX, para saber cuando debe parar. También se incrementa/decrementa el valor de los punteros usados (SI y/o DI). Dependiendo de la instrucción de que se trate, habrá que actualizar uno sólo de los punteros (STOS, LODS, SCAS) ó los dos (MOVS, CMPS). Y por qué digo: incrementa/decrementa? Pues porque los movimientos o comparaciones se pueden hacer hacia atrás o hacia delante. Cuando hacemos un movimiento/comparación hacia delante, a cada paso del bucle, se incrementan los registros SI y/o DI. Cuando hacemos un movimiento/comparación hacia atrás, a cada paso del bucle, se decrementan los registros SI y/o DI. Dependiendo del tamaño usado (byte o palabra), los incrementos o decrementos en los puneteros serán de 1 ó de 2 unidades, respectivamente. La forma en que el programador indica que los movimientos/comparaciones se realizarán hacia delante o hacia atrás, viene dada por la manipulación del flag Df. Para indicar que queremos que los movimientos/comparaciones se realicen hacia delante, debemos poner el flag Df con valor (0). Para indicar que queremos que los movimientos/comparaciones se realicen hacia atrás, debemos poner el flag Df con valor (1). Ya vimos la manera de modificar el valor del flag Df: STD ---> Pone el flag Df con valor (1). CLD ---> Pone el flag Df con valor (0). Ejemplo: Queremos mover 77 palabras (hacia adelante) desde la posición de memoria 7384h del segmento 8273h a la posición de memoria 7263h:8293h. La cosa quedaría así: MOV AX,7263H MOV ES,AX ;Registro ES con valor adecuado (7263h). MOV DI,8293H ;Puntero destino (DI) con valor adecuado (8293h). ;Dirección de destino en ES:DI (7263H:8293H). MOV AX,8273H MOV DS,AX ;Registro DS con valor adecuado (8273h). MOV SI,7384H ;Puntero (SI) con valor adecuado (7384h). ;Direcciones fuente y destino con su valor adecuado. MOV CX,77 ;Indicamos que queremos hacer 77 movimientos. CLD ;Los movimientos los vamos a hacer hacia delante. REP MOVSW ;Realiza 77 veces la instrucción MOVSW, actualizando ;debidamente los punteros fuente (SI) y destino (DI), ;tras cada iteración. Así mismo, decrementa el registro ;CX para saber cuando debe dejar de realizar repeticiones ;de la instrucción MOVSW. ;Cuando CX tenga valor Cero, dejará de realizar ;movimientos. ;En cada una de las 77 iteraciones, se coge la palabra ;contenida en DS:SI y la copia en ES:DI. ;Acto seguido, añade dos unidades a SI y a DI, para ;procesar el resto de las 77 palabras que componen el ;bloque que hemos indicado. + REPZ ó REPE (Repetir mientras CX <> 0, y Zf = 1). Repite una de las operaciones de comparación (operaciones de movimiento de cadenas no tienen sentido con REPZ) de cadenas tantas veces como indique el registro CX, siempre que el flag Zf sea 1. Es decir, se realizará la operación de comparación mientras CX sea distinto de Cero (aún queden elementos por comparar), y los dos elementos (bytes o palabras) comparados sean iguales. O sea, mientras que los elementos comparados sean iguales, y queden elementos por comparar, se procederá a comparar los siguientes. Ejemplo: Queremos comparar la cadena situada en DS:SI con la cadena situada en ES:DI. La longitud de la cadena será de 837 bytes. Supongamos que todos los registros de dirección tienen su valor adecuado. ;registros de dirección DS, SI, ES, DI con su valor adecuado. MOV CX,837 ;837 comparaciones de elementos de tipo byte. REPZ CMPSB ;Realiza la instrucción CMPSB mientras CX <> 0 y el ;flag Zf tenga valor (1), es decir: ;cada vez que se realiza una comparación, comprueba si ;los elementos comparados son iguales ('mirando' Zf), ;si no son iguales, deja de realizar comparaciones. ;Si son iguales entonces comprueba si quedan elementos ;por comparar (CX <> 0), en caso de que no queden, ;deja de realizar comparaciones. ;Tras cada comparación, actualiza debidamente los ;punteros fuente (SI) y destino (DI). ;En este caso se añade una unidad a cada uno de estos ;dos registros (SI y DI). JNZ diferentes ;Si tras la instrucción (REP CMPSB), el flag Zf ;tiene valor (0), eso quiere decir, que algún ;byte de la cadena fuente (DS:SI) no coincide con ;su correspondiente en la cadena destino (ES:DI). ;Es decir, las cadenas no son iguales. ;Entonces, realizamos un salto condicional a un ;trozo de código utilizado para el caso de que las ;cadenas sean diferentes. [...] ;grupo de instrucciones que se ejecutan cuando ;las cadenas son iguales. JMP fin_comparaciones ; salto el trozo de instrucciones que se ;ejecutan cuando las cadenas son diferentes. diferentes: ;aquí empieza el grupo de instrucciones que se ;ejecutan cuando las cadenas son diferentes. [...] ;grupo de instrucciones que se ejecutan cuando ;las cadenas son diferentes. fin_comparaciones: ;sigue el programa... La instrucción (REPZ CMPSB) ya veremos que es muy importante al tratar la programación de utilidades residentes. Mediante esta instrucción sabremos si ya ha sido instalado en memoria el programa. Simplemente hay que buscar un trozo del programa desde el principio de la memoria hasta la posición donde se encuentra el trozo a buscar. Si no se produce ninguna coincidencia, es porque el programa residente no está instalado. Si se produce una coincidencia, es porque el programa residente ya está instalado, con lo cual damos un mensaje al usuario (Programa ya instalado en memoria), y salimos a la linea de comandos otra vez. Pero bueno, ya veremos esto en profundidad al tratar los RESIDENTES. + REPNZ ó REPNE (Repetir mientras CX <> 0, y Zf <> 1). Repite una de las operaciones de comparación de cadenas tantas veces como indique el registro CX, siempre que el flag Zf sea 0. Es decir, se realizará la operación de comparación mientras CX sea distinto de Cero (aún queden elementos por comparar), y los dos elementos (bytes o palabras) comparados sean DIFERENTES. O sea, mientras que los elementos comparados sean diferentes, y queden elementos por comparar, se procederá a comparar los siguientes. Nota: Cuando realizamos comparaciones/movimientos de cadenas de longitud par, lo lógico sería hacerlo de palabra en palabra, mientras que si la longitud es impar, es imprescindible trabajar con bytes. ¿Alguna duda al respecto? Un último ejemplo de todo el tema de cadenas de caracteres: Queremos copiar la cadena origen (cadena_origen) al principio de Cadena_destino. ;******datos [...] Cadena_origen db 'SanBit V6.0 (Super Utilidades)' Cadena_destino db 'SanBit V5.6 (Utilidades residentes)' [...] ;******fin de datos. ;*****código de programa PUSH DS POP ES ;Mediante estas dos instrucciones, lo que hago es darle al ;registro ES el mismo valor que tiene DS. Esto se hace ya que ;las dos cadenas están dentro del mismo segmento. ;Suponemos que el registro DS estaba desde un principio apuntando ;al principio de los datos, como es normal. MOV SI,OFFSET Cadena_origen --- ;Mediante OFFSET, lo que hacemos es --- ;introducir en el registro SI, el desplazamiento (offset en --- ;inglés) de la variable Cadena_origen. Es decir, hacemos que SI ;contenga la dirección de Cadena_origen. Utilizamos SI como ;puntero a Cadena_origen. ;Por el contrario con la instrucción (MOV SI,Cadena_origen), lo ;que haríamos sería introducir la primera palabra contenida ;en Cadena_origen al registro SI. MOV DI,OFFSET Cadena_destino ;Hacemos que DI apunte a la variable ;Cadena_destino. Es decir, DI tendrá la dirección de la variable ;Cadena_destino. CLD ;Movimiento de datos hacia delante. MOV CX,15 ;15 es la mitad de 30 (longitud de Cadena_origen). REP MOVSW ;Realiza 15 movimientos de tipo palabra. Es decir, mueve 30 ;bytes desde Cadena_origen a Cadena_destino. ;Al trabajar con palabras en lugar de bytes, se gana mucho ;en velocidad, ya que el procesador tiene que utilizar el ;BUS la mitad de veces. ;******fin de código de programa. Tras la ejecución de este trozo de programa, la variable Cadena_Destino tendrá la siguiente cadena: 'SanBit V6.0 (Super Utilidades)entes)' Podemos observar que los últimos 6 caracteres permanecen intactos, mientras que los primeros 30 han sido 'machacados' por la instrucción, introduciendo en su lugar el contenido de Cadena_origen. Esto es todo por ahora.