Lenguaje Ensamblador para PC

Paul A. Cárter 18 de octubre de 2006
Copyright © 2001, 2002, 2003, 2004 by Paul Cárter Traducido al español por Leonardo Rodríguez Mújica. Sus comentaros y sugerencias acerca de la traducciún por favor a: lrodri@udistrital.edu.co Este documento puede ser reproducido y distribuido totalmente (incluida esta

paternidad literaria, copyright y aviso de autorizaciún), no se puede cobrar por este documento en sí mismo, sin el consentimiento del autor. Esto incluye una “utilizaciún racional” de extractos como revisiones y anuncios, y trabajos derivados como traducciones. Observe que esta restricción no esta prevista para prohibir el cobro por el servicio de impresion o copia del documento A los docentes se les recomienda usar este documento como recurso de clase; sin embargo el autor apreciaría ser notificado en este caso.

Prefacio
Propósito
El propósito de este libro es dar la lector un mejor entendimiento de como trabajan realmente los computadores a un nivel mas bajo que los lenguajes de alto nivel como Pascal. Teniendo un conocimiento profundo de como trabajan los computadores, el lector puede ser mós productivo desarrollando software en lenguajes de alto nivel tales como C y C++. Aprender a programar en lenguaje ensamblador es una manera excelente de lograr este objetivo. Otros libros de lenguaje ensamblador aón enseñan a programar el procesador 8086 que uso el PC original en 1981. El procesador 8086 solo soporta el modo real. En este modo, cualquier programa puede acceder a cualquier direccioón de memoria o dispositivo en el computador. Este modo no es apropiado para un sistema operativo multitarea seguro. Este libro, en su lugar discute como programar los procesadores 80386 y posteriores en modo protegido (el modo en que corren Windows y Linux). Este modo soporta las caracterósticas que los sistemas operativos modernos esperan, como memoria virtual y protección de memoria. Hay varias razones para usar el modo protegido 1. 2. 3. Es mas facil de programar en modo protegido que en el modo real del 8086 que usan los otros libros. Todos los sistemas operativos de PC se ejecutan en modo protegido. Hay disponible software libre que se ejecuta en este modos.

La carencia de libros de texto para la programacioón en ensamblador de PC para modo protegido es la principal razóon por la cual el autor escribióo este libro. Como lo dicho antes, este libro hace uso de Software Libre: es decir el ensamblador NASM y el compilador de C/C++ DJGPP. Ambos se pueden descargar de Internet. El texto tambien discute como usar el codigo del ensamblador NASM bajo el sistema operativo Linux y con los compiladores de C/C++ de Borland y Microsoft bajo Windows. Todos los ejemplos de estas

i

Debe descargar el código de los ejemplos. Julian Hall y otros por desarrollar el ensamblador NASM ya que todos los ejemplos de este libro estan basados en el. Todos los programe y aun este libro en sí mismo fueron producidos usando software libre. Gracias a las siguientes personas por correcciones: ■ John S. a DJ Delorie por desarrollar el compilador usado de C/C++ DJGPP. Tenga en cuenta que este libro no intenta cubrir cada aspecto de la programacion en ensamblador.com/pcasm. El autor ha intentado cubrir los tópicos mas importantes que todos los programadores deberían tener II Reconocimientos El autor quiere agradecer a los muchos programadores alrededor del mundo que han contribuido al movimiento de Software Libre. si desea ensamblar y correr los muchos ejemplos de este tutorial. El autor desearía agradecerle especialmente a John S. a Richar Stallman (fundador de la Free Software Fundation).PREFACIO plataformas se pueden encontrar en mi sitio web: http://www. la numerosa gente que ha contribuido al compilador GNU gcc en el cual esta basado DJGPP. Fine. Linus Torvalds (creador del núcleo de Linux) y a otros que han desarrollado el software que el autor ha usado para producir este trabajo.drpaulcarter. Simon Tatham. Fine ■ Marcelo Henrique Pinto de Almeida ■ Sam Hopkins ■ Nick D’Imperio ■ Jeremiah Lawrence ■ Ed Beroset ■ Jerry Gembarowski ■ Ziqiang Peng ■ Eno Compton ■ Josh I Cates ■ Mik Mifflin Luke Wallis . a Donald Knuth y otros por desarrollar los lenguajes de composicion de textos TeX y LTeX 2 £ que fueron usados para producir este libro.

com/djgpp http://www.linuxassembly.net/projects/nasm/ http://www.asm.ht Comentarios El autor agradece cualquier comentario sobre este trabajo.x8 6 http://developer.drpaulcarter.cs.drpaulcarter. E-mail: pacman128@gmail.com/pcasm .lang. Gotti ■ Bob Wilkinson ■ Markus Koegel ■ Louis Taber ■ Dave Kiddell ■ Eduardo Horowitz ■ Sebastien Le Ray ■ Nehal Mistry ■ Jianyue Wang ■ Jeremias Kleer ■ Marc Janicki Recursos en Internet Pagina del autor Pagina de NASM en SourceForge DJGPP Ensamblador con Linux The Art of Assembly USENET Documentación de Intel http://www.III ■ Gaku Ueda ■ Brian Heward ■ Chad Gorshing ■ F.com/ http://sourceforge.com/design/Pentium4/documentation.com WWW: http://www.delorie.edu/ comp.intel.org/ http://webster.ucr.

IV PREFACIO .

1.1 muestra cúomo se representan los primeros nuúmeros en binario. Porque se simplifica mucho el hardware.1. Cada dúgito de un número tiene una potencia de 10 asociada con el.1: Decimal de 0 a 15 en binario 1 . El Cuadro 1.Capítulo 1 Introducción 1. Sistemas de numeración La memoria en un computador esta compuesta de números. Primero haremos una revision del sistema decimal.1. basada en su posicion en el número. Cada dúgito de un número tiene una potencia de 2 asociada con el basada en su posicion en el número. Decimal Los números con base 10 estan compuestos de 10 posibles dúgitos (0-9).1. Binario Los números en base dos estan compuestos de dos posibles dúgitos (0 y 1). La memoria del computador no almacena estos números en decimal (base 10).2. 1.formaciún en binario (base 2). los computadores almacenan toda la in. Por ejemplo: 110012 = 1 x 24 + 1 x 23 + 0 x 22 + 0 x 21 + 1 x 2o = 16 + 8 + 1 = 25 Esto muestra cúomo los nuúmeros binarios se pueden convertir a decimal. Decimal Binary Decimal Binary 0 0000 8 1000 1 0001 9 1001 2 0010 10 1010 3 0011 11 1011 4 0100 12 1100 5 0101 13 1101 6 0110 14 1110 7 0111 15 1111 Cuadro 1. Por ejemplo: 234 = 2 x 102 + 3 x 101 +4 x 10o 1.

2: Conversión a decimal 1. Los hexadecimales (o hex) se pueden usar como una representación resumida de los números binarios. llamado bit menos significativo (lsb).1: Suma binaria (c es carry) CAPÍTULO 1.3. pero para los dígitos binarios de un námero. INTRODUCCION Sí hay carry antes 0 0 11 +0 +1 +0 +1 10 0 1 ccc La Figura 1. Dividiendo por dos hacemos una operación similar. Acá sigue un ejemplo: +100012 1011002 Si uno considera la siguiente divisián decimal: 1234 ^ 10 = 123 r 4 podemos ver que esta divisián suprime el dígito mas a la derecha de námero y desplaza los otros dígitos una posicián a la derecha.1.2 No hay carry antes 0 0 11 +0 +1 +0 +1 0 110 c Figura 1. Consideremos la siguiente division binaria1: 11012 ^ 102 = 1102 r 1 Este hecho se puede usar para convertir un námero decimal a su repre. Este metodo encuentra primero el bit del extremo derecho.2. Hexadecimal Los numero hexadecimales tienen base 16. El bit del extremo izquierdo es llamado bit mas significativo (msb).sentacián equivalente en binario como muestra la Figura 1.1 muestra como se suman los dígitos binarios individuales 110112 (bits). La unidad basica de memoria esta compuesta de 8 bits y es llamado byte Decimal Binary 25 ^ 2 = 12 r 1 12 ^ 2 = 6 r 0 6^2 = 3r0 3 ^2 = 1r 1 1 ^2 = 0 r 1 11001 ^ 10 = 1100 r 1 1100 ^ 10 = 110 r 0 110 ^ 10 = 11 r 0 11 ^ 10 = 1 r 1 1 ^ 10 = 0 r 1 Así 25io = 110012 Figura 1. Los xEl subíndice 2 se usa para mostrar que el número está representado en binario no en decimal .

Observe que ¡los ceros delanteros son importantes! Si los ceros del dígito de la mitad de 24D16 no se usan el resultado es erróneo. C. SISTEMAS DE NUMERACIÓN 3 números hexadecimales tienen 16 dígitos posibles. F. Cada dígito de un nímero hexadecimal tiene una potencia de 16 asociada con el. Convertir de binario a hex es así de fícil. Esto crea un problema ya que no hay símbolos para estos dígitos adicionales despues del nueve. Uno hace el proceso .1. B. 24D16 se convierta a 0010 0100 11012. Por ejemplo. Por convención se usan letras para estos dígitos adicionales.3 para un ejemplo. E. Vea la Figura 1. Los 16 dígitos hexadecimales son: 0-9 y luego A. Los hex son una manera mucho mas compacta de representar los nímeros binarios. D. Para convertir un nímero hexadecimal a binario simplemente convierta cada dígito hexadecimal a un nímero binario de 4 bits. Por ejemplo: 2BDI6 = 2 x 162 + 11 x 161 + 13 x 160 = 512 + 176 + 13 = 701 Para convertir de decimal a hex use la misma idea que la usada para la conversion binaria excepto que se divide por 16. B es 11 etc. El dígito A equivale a 10 en decimal.1. La razín por la cual los hexadecimales son ítiles es que hay una manera facil para convertir entre hex y binario. Los nímero binarios se tornan largos y molestos rápidamente.

Dos nibbles conforman un byte y por lo tanto un byte puede ser representado por dos dígitos hexadecimales. convierte cada segmento de 4 bits a hexadecimal comenzando desde el extremo derecho. Así cada dígito hexadecimal corresponde a un nibble. Los valores de un byte van de 0 a 11111111 en binario. del numero binario.3: inverso. 0 a FF en hex y 0 a 255 en decimal.4 589 • 16 CAPÍTULO 1. Ejemplo: 110 0000 0101 1010 0111 11102 6 0 5 A 7 Ei6 Un número de 4 bits es llamado nibble . INTRODUCCION = 36 r 13 =2r4 2 • 16 =0r2 • 3 1 Así 589 = 24Di6 Figura 1. no desde el izquierdo. 2Si no es claro porque el punto de inicio hace la diferencia. intente convertir el ejemplo . Esto asegura que el segmento de 4 bits es correcto 2.

Cada byte esta etiquetado por un número único conocido como su direcciúon.4: Direcciones de Memoria A menudo la memoria se usa en trozos mas grandes que un byte. en la arquitectura del PC. Un computador con 32 Mega bytes de memoria puede almacenar aproximadamente 32 millones de bytes de informacion. mega bytes ( 220 = 104 8 5 76 bytes) y giga bytes ( 230 = 1073741824 bytes).1.2.2. Tal como lo muestra la Figura 1. La Memoria La memoria es medida en unidades de kilobytes ( 210 = 1024 bytes). Direcciúon Memoria 5 01234567 2A 45 B8 20 8F CD 12 2E 1.1.2. ORGANIZACION DEL COMPUTADOR La unidad basica de memoria es el byte.2.4. . los nombres que se le han dado a estas secciones de memoria más grandes se muestran en la Tabla 1. Organización del computador 1. Figura 1.

que está reemplazando al ASCII es el Unicode. ORGANIZACION DEL COMPUTADOR word double word quad word paragraph 2 bytes 4 bytes 8 bytes 16 bytes 6 Cuadro 1. La CPU La Unidad Central de Procesamiento (CPU) es el dispositivo fásico que ejecuta las instrucciones. Las instrucciones que un tipo de CPU ejecuta las hace en lenguaje de maquina. Los programas escritos en otros lenguajes deben ser convertidos en lenguaje de máaquina nativo para que se ejecute en el computador. no para ser facilmente descifrados por humanos. Una diferencia clave entre los dos cádigos es que el ASCII usa un byte para codificar un carácter. Un compilador es un programa que traduce programas escritos en 3De hecho ASCII sálo usa los 7 bits más bajos y solo tiene 128 valores diferentes . Los caracteres son almacenados usando códigos de caracteres que traduce un námero en un carácter.2. Esto es importante para representar los caracteres de todas las lenguas del mundo. Sin embargo el námero de registros en la CPU es limitado. Los programas en lenguaje de maquina tienen una estructura mucho más basica que los lenguajes de alto nivel.2. Las instrucciones en lenguaje de maquina son codificadas como námeros no en formatos de texto amigables. Uno de los codigos de caracteres es conocido como ASCII (American Standar Code for Information Interchange). Las instrucciones pueden requerir datos que esten en un lugar especial de almacenamiento de la CPU en sí misma llamados registros. La CPU puede acceder a los datos en los registros mucho mas rápido que en la memoria. Un nuevo codigo. Una CPU debe estar en capacidad de decodificar una instruccion de proposito muy rápidamente para correr eficientemente. Los lenguajes de máquina son diseñados con este objetivo en mente . Unicode la codifica con la palabra 004116. así el programador debe tener cuidado de dejar solo los datos que este usando en los registros.2. Por ejemplo ASCII decodifica el byte 4116 (651o) en la A mayúscula. pero Unicode usa dos bytes (o una palabra) por carácter. 1.3 Unicode amplía los valores ASCII a palabras y permite que se representen muchos máas caracteres. mas completo.1. Las instrucciones que ejecuta la CPU son por lo general muy simples. Ya que ASCII usa un byte está limitado a solo 256 caracteres diferentes.2: Unidades de memoria Todos los datos en la memoria son numericos.

EDX. Agrega unas instrucciones nuevas al lenguaje de maquina base del 8080/86. Simplemente toca a una razún GHz tiene mil quinientos millones de pulsos de reloj constante. 1. Sin embargo la nueva característica principal nueva es el modo protegido de 16 bits. En este modo puede acceder hasta 16 Mega bytes de memoria y proteger los programas del acceso de otros. 8888. Ellas solo soportan hasta 1 Mega byte de memoria y solo opera en modo real. DS. En este modo pueden acceder hasta 4 Gigabyes. El número de toques (o como ellos llaman comúnmente ciclos) que una instrucciúon requiere depende de la instrucciúon anterior y de otros factores tambiúen. Una CPU de 1. Tambien añade un nuevo modo protegido de 32 bits. CS.GHz. EIP) y añade dos nuevos registros de 16 bits FS y GS. como el ritmo de un metrónomo para la por segundo. EBX. SS. compra un computador de 1. Ellos usan varios registros AX. El reloj pulsa a una frecuencia fija conocida como mil millones de ciclos por velocidad del reloj.. En este modo un programa puede acceder a cualquier direccion de memoria. ECX. BP. INTRODUCCION un lenguaje de programaciún a el lenguaje de maquina de una arquitectura en particular de un computador. 80386: Esta CPU es una gran ampliaciún del 80286. DX. ES.5 frecuencia de su reloj es 15. la electrónica de la CPU usa un ritmo para realizar sus operaciones correctamente. Esa es una de las razones por las cuales programas escritos para un Mac no corren en un PC tipo IBM Los computadores usan un reloj para sincronizar la ejecuciún de las GHz significa giga Hertz o instrucciones. DI. En general cada tipo de CPU tiene su propio y único lenguaje de maquina. la segundo. Sin embargo los miembros más recientes amplúan grandemente las características. Ellas fueron las CPUs usadas en las primeras PC. EBP. Cada segmento no puede ser mas largo que 64 KB 80286: Esta CPU se usa en los PC tipo AT. La familia de CPU 80x86 Las PC de tipo IBM tienen una CPU de la familia Intel (o un clon de ellas) Las CPU de esta familia todas tienen algunas caracterústicas comunes incluyendo el lenguaje de maquina básico.2. SI.5 GHz. EDI. aún a la memoria de otros programas Esto hace la depuracion y seguridad muy difícil. ESI. Los programas otra vez estan divididos en . BX. SP. ESP. Tambien la memoria del programa tiene que ser dividida en segmentos. Cuando Ud. FLAG. CX. Sin embargo los programas todavúa estan divididos en segmentos que no pueden ser mús grandes de 64K.8086: Estas CPU desde el punto de vista de la programaciún son iguales. ejecuciún de música al ritmo correcto. IP.7 CAPÍTULO 1. Primero extiende los registros para almacenar 32 bits ((EAX.3.

El registro AH contiene los 8 bits superiores de AX t AL contiene los 8 bits bajos de AX. Ellos senñalan quúe memoria es usada por diferentes partes de un programa.2. Pentium MMX: Este procesador añade instrucciones MMX (extensiones MultiMedia) al Pentium.5: El registro AX CAPÍTULO 1. CX y DX. Los registros de 16 bits BP y SP son usados para señalar a los datos en la pila y son llamados Apuntador Base (Base Pointer) y apuntador a la pila (Stack Pointer).6 y 1. Estas instrucciones pueden acelerar instrucciones comunes gráficas. Los registros de proposito general son usados en muchos movimientos de datos e instrucciones aritmúeticas. INTRODUCCION segmentos. SS Segmento de la pila (Stack Segment) y ES segmento extra (Extra Segment). ellos no se pueden descomponer en registros de 8 bits. Los detalles de estos registros estón en las Secciones 1.8 AX AH AL Figura 1. BX. ES es usado como un registro temporal. item[Pentium II:] Este es el procesador Pentium Pro con las instrucciones MMX añadidas (El pentium III es esencialmente solo un Pentium II rapido. 1.7. Ellos principalmente aceleran la ejecución de las instrucciones.4. A menudo AH y AL son usados como registros independientes de 8 bits sin embargo cambiando el valor de AX cambiara AH y AL y viceversa. Cada uno de esos registros puede ser descompuesto en los registros AL y AH que muestra la Figura 1. Registros de 16 bits del 8086 La CPU original 8086 suministra 4 registros de 16 bits de proposito general AX. CS significa segmento de cúdigo (code segment). Los registros de 16 bits CS. pero pueden ser usados para muchos de los mismos propúsitos como los registros generales. Hay dos registros de Indice de 16 bits SI y DI.2. DS. SS y ES son registros de segmento. DS segmento de datos (data segment). pero ahora cada segmento también puede ser hasta de 4 Giga bytes en tamaño 80486/Pentium/Pentium Pro: Estos miembros de la familia 80x86 añaden muy pocas características nuevas. respectivamente.2. Sin embargo. Ellos son a menudo usados como apuntadores. . Ellos se discutirán luego.5.

se decidióo dejar la definicióon de word sin cambio. EDX. auque el tamanño del registro cambióo.2. Sus nombres no significan nada. Por ejemplo el registro de 16 bits AX se extendió para se de 32 bits. The Instruction Pointer (IP) register is used with the CS register to keep track of the address of the next instruction to be executed by the CPU. Cuando se desarrollóo el 80386. ORGANIZACION DEL COMPUTADOR 9 El registro IP Apuntador a la instrucción (instruction pointer) es usado con el registro CS para obtener la direccion de la siguiente instruccion a ser ejecutada por la CPU. Estos resultados son almacenados como bits individuales en el registro. AX se refiere al registro de 16 bits y EAX se usa para referirse al registro extendido de 32 bits.1. en el modo protegido de 32 bits (discutidos abajo) solo se usan las versiones extendidas de estos registros.2. AX son los 16 bits inferiores de EAX tal como AL son los 8 bits inferiores de AX (y EAX). uno ve que word esta definida para ser 20 bytes (o 16 bits). Hay tambien dos nuevos registros de segmento: FS y GS. FLAGS en EFLAGS e IP en EIP. Por ejemplo el bit Z es 1 si el resultado de la instruccion anterior fue cero o 0 si el resultado no fue cero. IP is advanced to point to the next instruction in memory. El registro FLAGS almacena informacion importante sobre los resultados de una instrucción anterior. Modo Real ¿De donde viene el infame 1. Registros de 32 bits del 80386 El 80386 y los procesadores posteriores tienen registros extendidos.2.5. lómite de 640K de DOS? La En el modo real la memoria esta limitada a sólo 1 mega byte (220 BIOS requerida algunode bytes) 1M para el código y para los dispositivos de hardware como la pantalla de video . consulte la tabla en el apendice para ver cómo instrucciones específicas afectan el registro FLAGS 1. SP se convierte en ESP . as an instruction is executed.6. Para la familia 80x86. Normalmente cuando se ejecuta una instruccion IP avanza hasta señalar a la siguiente instruccion en memoria. Ellos son registros adicionales para segmentos temporales (como ES). Para la compatibilidad con sus predecesores. ESI and EDI. No hay forma de acceder directamente a los 16 bits superiores de EAX Los otros registros extetendidos son EBX. En la Tabla 1. ECX. Una de las definiciones del termno word se refiere a el tamño del registro de datos de la CPU. el termino es ahora un poco confuso.2. Los registros de segmento continóan siendo de 16 bits en el 80386. Sin embargo son diferentes los registros de óndice y los de propóosito general. Normally. cuando se lanzo la primera vez el 8086. Este fue el significado que se le dio. No todas las instrucciones modifican bits en FLAGS. Muchos de los otros registros se extienden tambien BP se convierte en EBP.

direcciones reales segmentadas tienen desventajas: ■ Un solo valor de selector súlo puede referenciar 64 K de memoria (el limite superior del desplazamiento de 16 bits). La direccion física 04804 puede ser referenciada por 047C:0048. En ambos modos. El primer valor de 16 bits es llamado selector.2). Los valores del selector deben estar almacenados en registros de segmento El segundo valor de 16 bits es llamado desplazamiento (offset) La direccion física referenciada por un par selector:desplazamiento es calculada por la formula: 16 * selector + offset multiplicar por 16 en hexadecimal es muy facil. El programa se debe dividir en secciones (llamadas segmentos menores de 64 K en tamaño.2. Otros datos y codigo son almacendos temporalmente en el disco hasta que ellos se necesiten de nuevo.10 CAPÍTULO 1. es súlo añadir un 0 a la derecha del número.7. un valor de selector es un número de púrrafo de memoria física. La idea baúsica de un sistema de memoria virtual. INTRODUCCION Las direcciones vúalidas estaún desde 0000 hasta FFFFF. Modo protegido de 16 bits En el modo protegido del 80286 los valores del selector son interpretados completamente diferente que en el modo En el modo real. 0047E:0028 o 047B:0058. es muy probable que se . Cuando la ejecucion se mueve de un segmento a otro los valores de CS se deben cambiar. los programas son divididos en segmentos. Esto puede complicar la comparaciún de direcciones segmentadas. Por ejemplo la direccion física referenciada por 047C:0048 esta dada por: 047C0 +0048 04808 En efecto. De hecho no tiene que estar todo el segmento en memoria. ¿Que pasa si un programa tiene mas de 64 K de cúdigo? Un solo valor en CS no se puede usar para toda la ejecucion del programa. En modo real estos segmentos estan en posiciones fijas en la memoria física y el selector denota el número de púrrafo de comienzo del segmento. 1. 047D:0038. Esto puede ser muy incúomodo ■ Cada byte de memoria no tiene una sola direccion segmentada. es dejar súolo los datos y el cúodigo que los programas estún usando en un momento dado. Cuando retorna un segmento a la memoria del disco. Intel soluciono este problema usando 2 valores de 16 bits para determinar una direccion. el valor selector es un número púrrafo (vea la Tabla 1. En el modo protegido un valor selector es un índice en una tabla de descripción. (en hexadecimal) Estas direcciones requieren un número de 20 bits Obviamente un número de 20 bits no cabra en ningún registro de 16 bits. El modo protegido usa una tecnica llamada memoria virtual . En modo protegido los segmentos no estan en posiciones fijas en la memoria física.

2. Modo protegido de 32 bits El 80386 introdujo el modo protegido de 32 bits. Esto hace problemútico el uso de arreglos grades.x el modo standar se refiere al modo protegido de 16 bits del 286 y el modo ampliado se refiere al modo de 32 bits. El sistema de memoria virtual trabaja ahora con paginas en lugar de segmentos.3. Todo esto es hecho transparementemente por el sistema operativo. Asú los segmentos pueden tener tamaños hasta de 4 gigabytes. El námero de la interrupcion es escencialmente un ándice en esta tabla. El úndice de la entrada del segmento es el valor del selector que estú almacendo en los registros de segmento. El programa no se tiene que escribir de otra manera para que la memoria virtual trabaje. etc) Las interrupciones hacen que el control se pase a un manipulador de interrupciones. 1.1. Las interrupciones externas son levantadas desde el exterior de la CPU (el . Interrupciones Algunas veces el flujo ordinario de un programa debe ser interrumpido para procesar eventos que requieren una respuesta rúapida. En Windows 3.8. Hay dos grandes diferencias entre los modos protegidos de un 386 de 32 bits y un 286 de 16 bits 1. A cada tipo de interrupciáon se le asigna un nuámero entero. Esta informaciún incluye: si estú ac. Los desplazamientos se amplúan a 32 bits. Esto permite un rango de desplazamiento hasta 4 billones. Esto no es practico con los grandes segmentos que permite el modo de 32 bits. Los manipuladores de interrupciones son rutinas que procesan la interrupciáon. si es asú dúnde estú. En el modo protegido a cada segmento se le asigna una entrada en una tabla de descriptores. Por ejemplo cuando se mueve el raton la interrupcion de hardware del ratán es el programa actual para manejar el movimiento del raton (para mover el cursor del mouse. Esto significa que solo partes de un segmento pueden estar en memoria a la vez.9. En el modo de 16 bits del 286 o todo el segmento estú en memoria o no esta. 2. Esta entrada tiene toda la informaciúon que el sistema necesita conocer sobre el segmento. En el comienzo de la memoria fásica una tabla de vectores de interrupcióon que contiene la direcciáon del segmento de los manipuladores de la interrupcion. permiso de acceso (ejem: súlo lectura). los tamaños muerto■ de los segmentos estún todavía limitados a un maximo de 64K. LENGUAJE ENSAMBLADOR 11 coloque en un area diferente de memoria en el que estuvo antes de ser enviada al disco. 1.2.tualemente en memoria. Los segmentos pueden ser divididos en unidades maús pequenñas de 4K llamadas paginas. Un conocido columnista de Una gran desventaja del modo protegido es que los desplazamientos estan PC Uarnó cd 286 “cxnbrv aún en cantidades de 16 bits Como una consecuencia de esto. El hardware de un computador provee un mecanismo llamado interrupcion para manipular estos eventos.

A menudo ellas acaban el programa. 1. Las instrucciones en lenguaje de maquina son námeros almacenados como bytes en memoria. disco duro CD ROM y tarjetas de sonido) Las interrupciones internas son levantadas desde la CPU. Muchas instrucciones incluyen tambien datos (ver constantes o direcciones) usados por las instrucciones. Por ejemplo la instruccion para sumar los registros EAX y EBX y almacenar el resultado en EAX está codificada por los siguientes codigos hexadecimales 4Sin embargo. Cada instruccion tiene su propio y ánico codigo llamado codigo de operacion u opcode. Descifrar el significado de las instrucciones codificadas numáericamente es tedioso para los humanos. desde una instruc. Las instrucciones del procesador 80X86 varían en tamaño. temporizador.struccioán de interrupciáon son llamadas interrupciones de sofware. Las trampas generalmente no retornan. ellas pueden usar una interfaz de bajo nivel (a nivel del kernel) .1.12 CAPÍTULO 1.3. Lenguaje ensamblador Lenguaje de maquina Cada tipo de CPU entiende su propio lenguaje de máquina. Muchos dispositivos de E/S levantan interrupciones (teclado.cion de error o desde una instruccion de interrupcion. 1. El opcode esta siempre al inicio de la instruccián. Ella restaura todos los registros con los mismos valores que tenían antes que ocurriera la interrupcion. DOS usa estas interrupciones paa implementar su API (Interfaz de programas de Aplicacion) Sistema operativos mas modernos (como Windows y Linux) usan una interfaz basada en C 4 Muchos manipuladores de interrupcion devuelven el control al programa interrumpido cuando ella culmina.3. Las instrucciones de error tambien se llaman trampas. El lenguaje de maquina es muy difícil de programar directamente. INTRODUCCION raton es un ejemplo de esto). Las instrucciones generadas desde la in.

Las instrucciones imaginarse cómo escribir un de un lenguaje de alto nivel son mucho mas complejas y pueden compilador requerir muchas instrucciones de míquina.3. sin embargo. Operandos de las instrucciones Los cúodigos de las instrucciones de múaquina tienen una variedad de tipos y operandos.3. Lenguaje ensamblador Un programa Escrito en lenguaje ensamblador es almacenado como texto (tal como programas de alto nivel). Por ejemplo.3. Cada instruccioún de lenguaje ensamblador científicos de la computación representa una sola instrucciúon de la múaquina. Los operandos pueden tener los siguientes tipos: .2. la instrucciúon de suma descrita arriba podría ser representada en lenguaje ensambaldor como: add eax. LENGUAJE ENSAMBLADOR 3 C3 13 Esto no es obvio.1. ebx Aca el significado de la instruccion es mucho mas claro que el codigo de la maquina. En los ejemplos de este libro se usa Netwide Assembler o NASM . Afortunadamente. 1. La palabra add es el nemónico nemúnico para la instrucciún de suma . Los compiladores son programas que hacen conversiones similares para lenguajes de programacioún de alto nivel. MASM y TASM . Otra diferencia importante entre los lenguajes ensamblador y de alto nivel es que debido a que cada tipo de CPU tiene su propio lenguaje de múaquina. tambiúen tiene su propio lenguaje ensamblador. en general cada instruccioún en si misma tiene un número fijo de operandos (0 a 3). La forma general de una instruccioún de ensamblaje es: mnemonico operando(s) Un ensamblador es un programa que lee un archivo de texto con instrucciones de ensamblador y convierte el ensamblador en cúodigo de múaquina. un programa llamado ensamblador puede hacer este aburrido trabajo para el programador. Trasladar programas entre arquitecturas de computador diferentes es mucho mas difícil que en un lenguaje de alto nivel. Los ensambladores maús comunes son el ensamblador de Microsoft (MASM) y el de Borland (TASM) . 1. Cada instrucciúon representa exactamente una instrucciúon de la múaquina.3. Un ensamblador es mucho múas simple Les tomo varios años a los que un compilador. Hay algunas diferencias en la sintaxis del ensamblador de NASM. Esta disponible libremente en internet (vea el prefacio para la URL).

edi Las instrucciones INC y DEC incrementan o decrementan valores en uno. instrucciones básicas La instruccion esencial es MOV . ecx++ . dl-- .1 0 sub ebx. src El dato especificado por src es copiado a dest. ax . implicado: Estos operandos no son mastrados explúcitamente. Hay a menudo algunas reglas arbitrarias sobre cúomo se usan las instrucciones. Una restriccion es que los dos operandos no pueden ser operandos de memoria. no en el segmento de datos. edi . El uno esta implúcito. Los operandos deben tener el mismo tamaño. Las direcciones son siempre desplazamientos relativos al comienzo de un segmento. el cúdigo de de maquina para INC y el DEC es mas pequeño que los de las instrucciones ADD y SUB.3. ebx = ebx . ah . memoria: Estos se refieren a los datos en la memoria. Aca hay un ejemplo(los . El valor de AX no puede ser almacenado en BL.4. Toma dos operandos: mov dest. immediato: Estos son valores fijos que estan listados en la instruccion en sú misma. Esto señala otra peculiaridad del ensamblador. almacena 3 en el registro EAX (3 es el operando inmediato) mov bx. sub bx. Por ejemplo. Ya que el uno es un operando implícito. almacena el valor de AX en el registro BX La instruccion ADD se usa para sumar enteros. inc ecx dec dl . INTRODUCCION registro: Estos operandos se refieren directamente al contenido de los registros de la CPU. 3 . Ellos son almacenados en la instrucciúon en si misma (en el segmento de codigo). eax = eax + 4 add al. inician un comentario) mov eax. al = al + ah La instruccion SUB resta enteros.14 CAPÍTULO 1. la instrucciúon de incremento anñade uno a un registro o a memoria. La direcciúon de los datos puede ser una constante fija en la instruccioún o puede ser calculada usando los valores de los registros. bx = bx . 1. add eax. 1 0 . 4 . Ella translada datos de un lugar a otro (como el operador de asignacion en un lenguaje de alto nivel).

. La directiva %define Esta directiva es parecida a la #define de C. Los súmbolos son constantes con nombre que se pueden emplear en el programa ensamblador.5. Se usa normalmente para definir macros tal como en C. Sin embargo las directivas del preprocesador de NASM comienzan con un como en C. "/„define SIZE 100 mov eax.3. Los usos comunes de las directivas son: • Definir constantes • Definir memoria para almacenar datos en ella • Definir la memoria para almacenar datos en ella • Agrupar la memoria en segmentos • Incluír codigo fuente condicionalmente • Incluír otros archivos El codigo de NASM pasa a traves de un preprocesador tal como C. Ellas no se traducen en cúdigo de míquina. Ellas se usan generalmente para decirle al ensamblador que haga alguna cosa o informarle al ensamblador de algo. directiva equ La directiva equ se puede usar para definir un súmbolo. Directivas Una directiva es un artificio del ensamblador no de la CPU. Los macros son mas flexibles que los símbolos de dos maneras. Los macros se pueden redefinir y pueden ser mas que simples constantes nuúmericas.15 CAPÍTULO 1. El formato es: símbolo equ valor Los valores de los símbolos no se pueden redefinir posteriormente. INTRODUCCION 1. SIZE El codigo de arriba define un macro llamado size y muestra su uso en una instruccion MOV. Tiene muchas de las úordenes del preprocesador tal como C.

2. Esta 5 Punto flotante de presicián simple es equivalente a la variable float en C. Las definiciones consecutivas de datos se almacenaráan secuencialmente en memoria. "o". ’d’. Abajo hay varios ejemplos. . igual que L10 La directiva DD se puede usar para definir o enteros o constantes de punta flotante de presicián simple.5 Sin embargo DQ solo se puede usar para definir constantes de punta flotante de doble precisiáon. Esto es. Las etiquetas le permiten a uno referirse facilmente a lugares de la memoria en el codigo. La X se reemplaza con una letra que determina el tamaño del objeto (u objetos) que sera almacenados.16 Unidad byte word double word quad word ten bytes CAPITULO 1. la segunda manera define el espacio y el valor inicial. define una cadena tipo C = "word" L11 db ’word’. 0 . 0 . Es muy comán marcar lugares de memoria con etiquetas. Las X son las mismas que las de la directiva RESX.3 muestra los valores posibles. Para secuencias largas la directiva TIMES de NASM es a menudo útil. Hay dos formas en que la memoria puede ser reservada. El segundo metodo (que define un valor inicial tambien) usa una de las directivas DX. INTRODUCCIÓN Letra B W D Q T Cuadro 1. define 4 bytes L10 db "w". 1. la palabra L2 se almacenara inmediatamente despues que la L1. 3 . Se pueden definir tambiáen secuencias de memoria. L9 db 0. "r". El primer metodo usa una de las directivas RESX. La tabla 1.3: Letras para las directivas RESX y DX Directivas de datos Las directivas de datos son usadas en segmentos de datos para definir espacios de memoria. La primera es solo definir el espacio para los datos. L1 L2 L3 L4 L5 L6 L7 L8 db dw db db db dd resb db 0 1000 110101b 12h 17o 1A92h 1 "A" byte etiquetado como L1 con valor inicial 0 palabra etiquetada como L2 con valor inicial de 1000 byte con valor inicial binario de 110101 (53 en decimal) byte con valor inicial hex de 1 2 (18 en decimal) byte con valor inicial octal de 17 (15 en decimal) plabra doble con valor inicial hex de 1A92 un byte sin valor inicial byte con valor inicial del código ASCII para A (65) Las comillas dobles o simples se interpretan igual.

Otros especificadores son: BYTE. uno podría pensar de una etiqueta como un apuntador al dato y los paréntesis cuadrados como la des referencia al apuntador tal como el asterisco lo hace en C (MSSM y TASM siguen una convenciún diferente). [L1] eax. Para definir esto. copia el byte que está en L1 en AL EAX = direccián del byte en L1 copia AH en el byte en L1 copia la palabra doble en L6 en EAX EAX = EAX + la palabra doble en L6 la palabra doble en L6 += EAX copia el primer byte de la palabra doble en L6 en AL La lúnea 7 de los ejemplos muestra una propiedad importante de NASM. 6 6TWORD define un área de memoria de 10 bytes.3. En el modo de 32 bits las direcciones son de 32 bits. ¿Por que? Porque el ensamblador no sabe si almacenar el 1 como byte. ah eax. [L6] eax. El coprocesador de punto flotante usa este tipo de dato.1. . . Si la etiqueta es colocada dentro de paréntesis cuadrados ([]). Una vez múas no se verifica que el apuntador se use correctamente. 1 2 3 4 5 6 7 mov mov mov mov add add mov al. [L6] [L6]. Considere la siguiente instruccioún: mov [L6]. QWORD Y TWORD. se añade un especificador de tamaño . . eax al. se interpreta como el dato en la direccion. . [L6] . WORD. reserva lugar para 100 palabras Recuerde que las etiqueta pueden ser usadas para referirse a datos en el codigo. Si se usa una etiqueta esta es interpretada como la direccion (o desplazamiento) del dato. Luego serúa comuún almacenar direcciones de datos en registros y usar los registros como una variable apuntador en C. equivalente a 100 veces db 0 . . LENGUAJE ENSAMBLADOR 17 directiva repite su operando un nuúmero especificado de veces por ejemplo: L12 times 100 db 0 L13 resw 100 . 1 . L1 [L1]. . De este modo el ensamblador es mucho mús propenso a errores aún que C. De tal forma que el programador debe estar seguro que usa la etiqueta correctamente. . mov dword [L6]. El ensamblador no recuerda el tipo de datos al cual se refiere la etiqueta. palabra o palabra doble. almacena 1 at L6 Esto le dice al ensamblador que almacene un 1 en la palabra doble que comienza en L6. 1 . Aca hay algunos ejemplos. almacena 1 en L6 Esta instruccion produce un error de tamaño no especificado. En otras palabras.

inc" Para usar una de las rutinas print. Ellas deben acceder directamente al hardware (que es una operaciúon privilegiada en el modo protegido) o usar rutina de bajo nivel que provea el sistema operativo. Estas reglas son muy complicadas para cubrir acaú (ellas se verúan luego) . Sin embargo uno debe conocer las reglas que usa C para pasar informacioún entre rutinas.drpaulcarter. imprime en pantalla el caracter de nueva línea. Para usar estas rutinas uno debe incluir un archivo con la informaciúon que el ensamblador necesita usarlas. Estas rutinas modifican el valor del registro EAX. como C. Para incluir un archivo en NASM use la directiva del preprocesador %include. proveen bibliotecas normalizadas de rutinas que suministran una interfas de programaciúon simple y uniforme para la dE/ S. Una de las ventajas de esto es que el cúodigo en ensamblador puede usar las rutinas E/S de las bibliotecas estandar de C. La siguiente línea incluye el archivo necesario para las rutinas de E/S hechos por el autor http://www. La tabla 1. Hace un salto en la ejecuciún hacia otra seccion de codigo pero despues retorna al origen luego . lee un entero del teclado y lo almacena en el registro. Es muy común que se interfacen rutinas de ensamblador con C. La cadena debe ser tipo C.4: Rutinas de E/S en ensamblador 1. uno carga EAX con el valor correcto y usa la instruccioún Call para invocarla.18 print_int print_char print_string CAPITULO 1. Todas las rutinas preservan el valor de todos los registros .com/pcasm: /include "asm_io. Los lenguajes ensamblador no disponen de bibliotecas normalizadas.3.6.4 describe las rutinas suministradas. INTRODUCCIÓN imprime en la pantalla el valor del entero almacendo en EAX imprime en la pantalla el caracter cuyo codigo ASCII estúe almacendo en AL imprime en la pantalla el contenido de la cadena en la dirección almacenada en EAX. terminada en NULL). Entrada y Salida La entrada y salida son acciones muy dependientes del sistema . Involucra comunicarse con el hardware del sistema. Los lenguquajes del alto nivel. lee un solo caracter del teclado y almacena el coúdigo ASCII en el registro EAX. Para simplificar la E/S el autor ha desarrollado sus propias rutinas que ocultan las complejas reglas de C y provee una interfas mucho mís simple. excepto las rutinas de lectura. print_nl read_int read_char Cuadro 1.La instrucciúon Call es equivalente a un llamado de funcion en un lenguaje de alto nivel.

Torma un solo entero como parúametro que luego se imprime. return 7El capítulo 2 discute este registro . 7 Por ejemplo si la bandera cero es 1 se muestra ZF. Si es cero no semuestra nada. El segundo argumento es la direcciúon a mostrar (esta puede ser una etiqueta). dump_math Este macro imprime los valores de los registros del coproce. Tambien imprime el estado de los bits del registto FLAGS. El último argumento es un número de l6 bytes para mostrar luego de la direcccioún. dump_stack Este macro imprime los valores de la pila de la CPU (la pila se vera en el capútulo 4).4.3. Toma tres parámetros separados por comas. Este entero se puede usar para distinguir la salida de diferentes ordenes dump_regs. El primero es un entero que es usado para identificar la salida (tal cual como el argumento de dump_regs). Depuración La biblioteca del autor tambien contiene algunas rutinas útiles para depurar los programas.7. El primero es un identificador entero (como dump_regs).sador matemútico. y el tercer argumento es el nuúmero de palabras dobles a imprimir sobre la direcciún de EBP. Ellas muestran los valores de los registros. Los matros se usan como instrucciones normales. Los macros estan definidos en el archivo code asm_io. dump_mem. El segundo es el número de palabras dobles para mostrar luego de la direcciún que tenga almacenada el regisstro EBP. La pila esta organizada como palabras dobles y esta rutina las mostrara de esta forma. Hay cuatro rutinas de depuraciún llamadasdump_regs. La memoria mostrada comenzarúa en el primer lúmite un púrrafo antes de la direccion solicitada. Estas rutinas de depuracion muestran informacion sobre el estado del computador sin modificar su estado. pila y el coprocesador matemúatico respctivamente dump_regs Este macro imprime los valores de los registros (en hexadecimal) del computador stdout (la pantalla). dump_mem Este macro imprime los valores de una region de memoria (en hexadecimal) y tambien como caracteres ASCII.1. ret_status = asm_main(). Estas rutinas son en realidad macros que muestran el estado de la CPU y luego hacen un llamado a una subrutina. 19 1. CREANDO UN PROGRAMA que la rutina a culminado. memoria. Los operandos de los macros se separan con comas. Toma tres argumentos delimitados por comas. Toma un solo parámetro entero como argumenint main() { int ret_status .inc discutido antes. El programa muestra varios ejemplos de llamadas de estas rutinas de E/S. dump_stack and dump_math.

Los ultimos dos puntos demuestran que aprender ensamblador puede ser util aán si uno nunca programa en el mas.6. El ensamblador permite acceder directamente a características del hardware del sistema que puede ser difícil o imposible de usar desde un lenguaje de alto nivel. Aprender a programar en ensamblador le ayuda a uno a ganar un entendimiento profundo de cámo trabaja el computador. 1.1. } Figura 1. 2. el autor raramente programa en ensamblador pero usa las ideas aprendidas de el todos los días. ¿Por que alguien quisiera aprender ensamblador? 1. De hecho.4. 1. Algunas veces el codigo escrito en ensamblador puede ser mas rápido y pequeño que el codigo generado por un compilador. Primer programa Los primeros programas en este texto comenzaran todos con un programa sencillo de C mostrado en la Figura 1.6: codigo de driver. INTRODUCCION ret_status . De hecho es raro usar el ensamblador en todo. 3. 4.c to que se usa para identificar la salida tal como el argumento de dump_regs lo hace. ¿Por que? Es mucho mas fácil programar en un lenguaje de alto nivel que en ensamblador. Tambien al usar ensamblador es muy difícil transportar el programa a otras plataformas. Creando un programa Hoy día no es comán crear un programa independiente escrito totalmente en lenguaje ensamblador. Simplemente llama otra funcion .4. El ensamblador es usado para desarrollar ciertas rutinas crítica.20 CAPÍTULO 1. Aprender a programar en ensamblador ayuda a entender mejor como trabajan los compiladores y los lenguajes de alto nivel como C.

CREANDO UN PROGRAMA 21 llamada asm_main. They use C’s I/O functions (printf.o driver.4. 0 "Ud. El código en ensamblador no necesita preocuparse de nada de esto. The author’s I/O routines take advantage of this. Las rutinas de E/S del autor aprovechan esto. Primero dejamos que C fije todos los parámetros para que el programa se ejecute correctamente en el modo protegido. This is really a routine that will be written in assembly.asm . The assembly code need not worry about any of this.o %include "asm_io. ha digitado ". 0 . this lets the C system set up the program to run correctly in protected mode. The following shows a simple assembly program. There are several advantages in using the C driver routine. the C library will also be available to be used by the assembly code. no olvide el fin de cadena "Digite un numero: ".data Estas etiquetas se refieren a las cadenas usadas para la salida promptl prompt2 outmsgl outmsg2 outmsg3 db db db db db .). It simply calls another function named asm_main. 0 ".inc" Los datos iniciados se colocan en el segmento . Esta es la rutina escrita en ensamblador.asm Primer programa en ensamblador.c asm_io.). First. nasm -f coff first. Para crear el ejecutable usando djgpp: . la suma es ". Este programa pide dos enteros como entrada e imprime su suma .asm --------------------------------------------------------Archivo: first. Secondly. Ellas usan las funciones de E/S de C (printf. Ahora se muestra un programa elemental en ensamblador. Hay varias ventajas de usar este programa en C. etc.6. gcc -o first first.first.1. Todos los segmentos y sus correspondientes registro de segmento serán iniciados por C. 0 " y ".data segment . ----------------------------------------------. The early programs in this text will all start from the simple C driver program in Figure 1. etc. All the segments and their corresponding segment registers will be initialized by C. Segundo las bibliotecas de C estaran disponibles para ser usadas en el código de ensamblador. 0 "Digite otro numero: ".

INTRODUCCION .text global _asm_main _asm_main: enter 0. print out prompt read_int [inputl]. . lee un entero . eax eax. print out prompt read_int [input2]. [inputl] [input2] ebx. lo almacena en input2 eax. Los datos no iniciados se colocan en el segmento .0 pusha mov call call mov mov call call mov mov add mov eax. print_string .bss . lee un entero .22 CAPÍTULO 1. eax . eax += dword en input2 . print_string . eax eax. lo almacena en input1 prompt2 . outmsgl. se imprimen los resultados en una serie de pasos . setup routine promptl .text segment . ebx = eax dump_regs 1 . eax = dword en inputl . imprimer la memoria ahora. imprime los valores de los registros dump_mem 2.bss segment . de entrada inputl resd 1 input2 resd 1 . 1 . Estas etiquetas señalan a palabras dobles usadas para almacenar los datos . El código se coloca en el segmento .

eax.bss en la línea 26). Es muy importante conocer esta convencion cuando se interfaza C con ensamblador. Luego se presentara toda la convencion. Esto significa que solo el codigo en el mismo modulo . sin embargo por ahora uno solo necesita conocer que todos los símbolos de C ( funciones y variables globales ) tienen un guiín bajo como prefijo anexado a el por el compilador de C. 23 se imprime el primer mensaje se imprime inputl se imprime el segundo mensaje se imprime input2 se imprime el tercer mensaje se imprime la suma (ebx) se imprime una nueva linea La línea 13 del programa define una sección del programa que especifica la memoria al ser almacenada en el segmento de datos ( cuyo nombre es . Existe tambien el segmento de la pila. eax.data ). Como en C. outmsgl print_string . print_nl . eax.4. Acá es donde se colocan las instrucciones. retorna a first. eax. [input2] print_int . Este segmento toma su nombre de un operador de ensamblador basado en UNIX que significa “block started by simbol”.Sera discutido despues. as m C eax. [inputl] print_int . Esto es parte de las convenciones de llamado de C. Recuerde que hay una gran diferencia entre 0 y ’0’. El segmento de cádigo es llamado . En las líneas 17 a 21 se declaran varias cadenas. (Esta regla es específica para Dos/windows. las etiquetas tienen un alcance interno por defecto. Ellas serán impresas con las bibliotecas de C y como tal deben estar terminadas con el caracter null (el codigo ASCII 0). Solo los datos iniciados se deberían definir en este segmento. 0 . el compilador de C de linux no antepone nada a los nombres de los símbolos).text por razones histáricas. outmsg3 print_string . ebx print_int . Observe que la etiqueta de la rutina principal (línea 38) tiene un prefijo de guion bajo. La directiva global en la línea 37 le dice al ensamblador que tome la etiqueta _asm_main como global. Esta convencion especifica las reglas que usa C cuando compila el codigo. outmsg2 print_string . CREANDO UN PROGRAMA mov call mov call mov call mov call mov call mov call call popa mov leave ret eax.1. Los datos no iniciados deberían declararse en el segmento bss (llamado . eax.

9 Este compilador puede ser descargado libremente de internet.fsf. Tambien produce un objeto con una extension o. El segmento para que trabajen con el data (línea 13) se debe cambiar a: compilador apropiado. archivos de un Borland C/C++ es otro compilador popular. Use la opcion -f win 32 para este formato. El modulo asm_io declara las etiquetas print_int etc..deloire. INTRODUCCION puede usar la etiqueta.4.asm. Este tipo de etiqueta puede ser alcanzado por cualquier modulo en el programa. La extension del archivo objeto sera obj. Requiere un PC 386 o posterior y se ejecuta bajo Dos. Use la opcion -f elf para linux. El formato win 32 permite que los segmentos se definan tal como DJGPP y linux. Use la opcion -f obj para los compiladores de disponibles en la pagina web Borland.24 CAPÍTULO 1. Windows 95/98 o NT. Para convertir el codigo para que corra bajo linux simplemente quita los guion bajos de prefijos en las líneas 37 y38.org) http://www.cadas de (s) un alcance externo. globales . Este compilador usa archivos objeto con formato COFF (common objet file format). Usa el formato de OMF de Los compilador dado están microsoft para los archivos objeto. Este es el porque uno puede usarlas en el modulo first. 1.2. Dependencias del compilador El codigo de ensamblador de arriba es específico del compilador libre GNU C/C++8 DJGPP . Para ensamblar este formato use la opcion -f coff con nasm (como se muestra en los comentarios del cúdigo). El formato OMF utiliza unas del autor ya modificados directivas de segmento diferentes que los otros formatos de objetos. Linux usa el formato ELF (Excecutable an Linkable Format) para los archivos objetos. El compilador de C de linux tambien es GNU. el convierte la informacion internamente en win 32). segment _DATA public align=4 class=DATA use32 el segmento bss (line 26) se debe cambiar a: segment _BSS public align=4 class=BSS use32 El segmento text (line 36) se debe cambiar a: segment _TEXT public align=1 class=CODE use32 Ademas se debe añadir una nueva línea antes de la línea 36. 8GNU es un proyecto de la Free Software Foundation (http://www. group DGROUP _BSS _DATA El compilador de Microsoft C/C++ puede usar el formato OMF o el win 32 para los archivos objeto (si le dan un formato OMF. La extensiún del archivo objeto resultante serúa o. La extensiún del archivo objeto sera obj.com/djgpp . La directiva global da a la (s) etiqueta (s) específi.

Desde la línea de orden digite . este proceso es complicado. Compilando el codigo de C Compile el archivo driver.4. Este archivo muestra címo se ensamblí el codigo. 0 . El codigo de C requieren la biblioteca estandar de C y un código de inicio especial para ejecutarse. Entender un archivo de listado de ensamblador La opciín -l archivo-de-listado se puede usar para decirle a nasm que cree un archivo de listado con un nombre dado. Los nímeros de las líneas estín en el archivo de listado. digite: gcc -o first driver. Como se vera adelante. 1.obj asm_io. Se muestra como las líneas 17 y 18 (en el segmento data) aparecen en el archivo de listado.c usando un compilador de C.obj driver.4. no intente encadenar aán.C y entonces lo encadenara.o asm_io.asm Donde el formato del objeto es coff . 1. y Microsoft tambien.c La opcion -c significa que solo compile. Es mucho mas facil dejar que el compilador de C llame al encadenador con los parámetros correctos que intentar llamar al encadenador directamente. Para DJGPP. (Recuerde que tambien se deben cambiar los archivos fuente por linux y Borland tambien).c first. sin embargo observe que los nímeros de las líneas en el archivo fuente pueden no ser los mismas que las del archivo de listado.o Ahora gcc compilara driver. Por ejemplo: gcc -o first driver.obj Borland usa el nombre del primer archivo en la lista para determinar el nombre del ejecutable.4.o asm_io. obj o win 32 dependiendo que compilador de C sera usado. Por ejemplo encadenar el cádigo para el primer programa utilizando DJGPP. digite: gcc -c driver.1.4.promptl db "Enter a number: ". Esta misma opcion trabaja en los compiladores de Linux.4. Borland. CREANDO UN PROGRAMA 25 1.6.exe (o solo first bajo Linux). Con Borland uno usaría: bcc32 first.elf. Así en el caso anterior el programa debería llamarse first.3.5. nasm -f formato-de-objeto first.4. Ensamblando el código El primer paso es ensamblar el codigo. Es posible combinar el paso de compilar y encadenar.o Esto crea un ejecutable llamado first.exe.o first. encadenando los archivos objeto El encadenamiento es un proceso de combinar el cádigo de máquina y los datos en archivos objeto con archivos de biblioteca para crear un archivo ejecutable. 48 00000000 456E7465722061206E. 1.

26

CAPÍTULO 1. INTRODUCCION

49 50 51 52

00000009 756D6265723A2000 00000011 456E74657220616E6F- prompt2 db "Enter another number: ", 0 0000001A 74686572206E756D6200000023 65723A2000 La primera columna en cada línea es el nímero de línea y la segunda es el desplazamiento (en hex) de los datos en el segmento. La tercera columna muestra los valores en hexadecimal que serían almacenados. En este caso el dato hexadecimal corresponde a codigos ASCII. Finalmente en la línea se muestra el texto del codigo fuente. Los desplazamientos mostrados en la segunda columna son muy probables que no sean los desplazamientos reales, los datos seran colocados en el programa completo. Cada mídulo puede definir sus propias etiquetas en el segmento de datos ( y en los otros segmentos tambien). En el paso de encadenamientoi vea la Seccion 1.4.5 , todas estas definiciones de segmentos y etiquetas son combinadas para formar un solo segmento de datos. El encadenador entonces calcula el desplazamiento definitivo. Se muestra una pequeña seccion (líneas 54 a 56 del archivo fuente) del segmento de texto en el archivo de listado.

94 0000002C A1[00000000] 95 00000031 0305[04000000] 96 00000037 89C3

mov eax, [input1] add eax, [input2] mov ebx, eax

La tercera columna muestra el codigo de maquina generado por el ensamblador. A menudo el codigo completo de una instruccion no se puede calcular aín. Por ejemplo, en la línea 94 el desplazamiento (o direccion) de input1 no se conoce hasta que el codigo se encadene. El ensamblador puede calcular el codigo de la instrucciín mov (que del listado es A1), pero escribe el desplazamiento en paréntesis cuadrados porque el valor exacto no se puede calcular en este momento. En este caso se utiliza un desplazamiento temporal de 0 porque input1 esta al inicio de la parte del segmento bss definido en este archivo. Recuerde que esto no significa que estaría al comienzo del segmento bss definitivo del programa. Cuando el codigo es encadenado, el

1.4. CREANDO UN PROGRAMA

27

encadenador insertara el desplazamiento en la posicion correcta. Otras instrucciones como la línea 96 no hacen referencia a ninguna etiqueta. Aca el ensamblador puede calcular el codigo de máquina completo. Representaciones Big y Little Endian Si uno mira de cerca en la línea 95 hay algo muy extraño sobre el desplazamiento en los paréntesis cuadrados del codigo de máquina. La etiqueta input2 tiene un desplazamiento de 4 (como está definido en este archivo); sin embargo, el desplazamiento que aparece en la memoria no es 0000004, pero 04000000. ¿Por que? Diferentes procesadores almacenan enteros de varios bytes en ordenes diferentes en la memoria. Existen dos metodos populares se pronuncia como de almacenar enteros: big endian y littel endian. Big endian es el metodo que se ve mas natural. El byte mayor (mas significativo) se almacena primero, y luego los siguientes. Por ejemplo, la palabra doble 00000004 se debería almacenar como los cuatro bytes 00 00 00 04. Los mainframes IBM, la mayoría de los procesadores RISC y los procesadores Motorola todos ellos usan el metodo de Big endian. Sin embargo los procesadores Intel usan el metodo little endian. Aca se almacena primero el byte menos significativo. Así 00000004 se almacenara en memoria como 04 00 00 00. Este formato esta cableado en la CPU y no se puede cambiar. Normalmente el programador no necesita preocuparse sobre que formato esta usando. Sin embargo hay circunstancias donde esto es importante. 1. 2. Cuando un dato binario es transfiere entre computadores diferentes (o de archivos o a traves de una red) Cuando un dato binario es escrito fuera de la memoria como un entero multibyte y luego se vuelve a leer como bytes individuales o vice versa

Endian
indian.

Lo Endian no se aplica al orden de los elementos de un arreglo. El primer elemento de un arreglo está siempre en la direccion menor. Esto se aplica a cadenas (que solo son arreglos de caracteres). Lo Endian solo se aplica a los elementos idividuales de un arreglo.

1.5.

Archivo esqueleto

La Figura 1.7 muestra un archivo esqueleto que se puede usar como punto de partida para escribir programas en ensamblador

skel.asm /include "asm_io.inc" segment .data ; los datos iniciados se colocan en el segmento de ; datos aca

segment .bss ; Datos no iniciados se colocan en el segmento bss segment .text global _asm_main _asm_main: enter 0,0 pusha

; rutina de

El código esta colocado en el segmento de texto. No modifique el codigo antes o despues de este comentario

eax, 0

; retornar a C
skel.asm

leave ret

Figura 1.7: Programa esqueleto

popa mov

ARCHIVO ESQUELETO CAPITULO 1.5.28 Capítulo 2 1. INTRODUCCION 29 .

Representa los enteros como dos partes. las dos representaciones podrían servir igual. la aritmetica general tambien es complicada.1. Primero hay dos valores posibles de cero +0 (00000000) y -0 (10000000). El número 200 como un byte entero sin signo sería representado como 11001000 (o C8 en hex). Por ejemplo considere -56. Complemento a uno El segundo metodo es conocido como complemento a uno. Ya que cero no es ni positivo ni negativo. Segundo. El complemento a uno de un nímero se encuentra invirtiendo cada bit en el nímero. +56 como byte sería representado por 00111000. Para negar un valor se cambia el bit del signo.30 CAPITULO 2. . Magnitud y signo El primer metodo es el mís elemental y es llamado magnitud y signo. El mayor valor de un byte sería 01111111 o +127 y el menor valor sería 11111111 o -127. pero tiene sus inconvenientes. En el papel uno podría representar -56 como 111000. En la notacion de complemento a uno calcular el complemento a uno es equivalente a la negacion. esto complica la logica de la CPU. Una vez mas. Los enteros con signo (que pueden ser positivos o negativos) se representan de modo mís complejo. Si se añade 10 a -56.1. (Otra manera de ver esto es que el nuevo valor del bit es 1 — elvalorantiguodelbit). Por ejemplo el complemento a uno de 00111000 (+56) es 11000111. Esto complica la logica de la aritmetica para la CPU. Los enteros sin signo (que son no negativos) estín representados de una manera muy sencilla en binario. pero ¿como podría representarse en un byte en la memoria del computador? ¿Como se almacenaría el signo? Hay 3 tecnicas que se han usado para representar números enteros en la memoria del computador. este debe transformarse en la resta de 10 y 56. La primera es el bit de signo y la segunda es la magnitud del entero. Así 56 sería representado como el byte 00111000 (el bit de signo esta subrayado) y -56 sería 10111000. LENGUAJE ENSAMBLADOR BÁSICO Lenguaje ensamblador básico 2.1. 2. Trabajando con enteros Representación de enteros Hay dos tipos de enteros: sin signo y con signo. Este metodo es sencillo. Todos estos metodos usan el bit mas significativo como un bit de signo. Este bit es 0 si el número es positivo y 1 si es negativo.

Observe que el bit de signo fue cambiado automaticamente por el complemento a uno y que como se esperaría al aplicar el complemento a 1 dos veces produce el nímero original. Así 11001000 es la representacion en complemento a dos de —56. Complemento a dos Los dos primeros metodos descritos fueron usados en los primeros computadores. Esto es coherente con el resultado anterior. Ací esta un ejemplo usando 00111000 (56).1: Representacion de complemento a dos Sorprendentemente el complemento a dos no reíne este requisito. 2. . Dos negaciones deberían reproducir el nímero original. La aritmetica con numeros en complemento a uno es complicada. Number Hex Representation 0 00 1 01 127 7F -128 80 -127 81 -2 FE -1 FF Cuadro 2. Primero se calcula el complemento a uno: 11000111.2. Los computadores modernos usan un tercer metodo llamado la representacion en complemento a 2. Como el primer metodo hay dos representaciones del cero 00000000 (+0) y 11111111 (—0). Tome el complemento a dos de 11001000 añadiendo uno al complemento a uno. Un ejemplo: +56 se representa por 38 en hex. Hallar el complemento a uno del nímero. Hay un truco útil para encontrar el complemento a 1 de un nímero en hexadecimal sin convertirlo a binario. Entonces se añade uno: 11000111 _+ _________ 1_ 11001000 En la notacion de complemento a dos. El complemento a 2 de un nímero se halla con los dos pasos siguientes: 1. Para encontrar el complemento a 1 reste F de cada dígito para obtener C7 en hexadecimal. Este metodo supone que el nímero de dígitos binarios en el nímero es un míltiplo de 4.1. Sumar uno al resultado del paso 1. El truco es restar el nímero hexadecimal de F (o 15 en decimal) . TRABAJANDO CON ENTEROS 31 Así 11000111 es la representacion de —56. calcular el complemento a dos es equivalente a negar el nímero.

Este carry no se usa. Reduciendo el tamaño de los datos . -128 como FF80 y -1 como FFFF.768 por 8000.767 que está representado por 7FFF. La Tabla 2.1 muestra algunos valores seleccionados. El ensamblador no tiene ni idea de los tipos de datos que tienen los lenguajes de alto nivel. No es raro necesitar cambiar el tamanño del dato para usarlo con otro dato. Cúmo se interpretan los datos depende de que instrucciún se usa con el dato. Recuerde que todos los datos en el computador son de un tamanño fijo (en terminos de nímeros de bits). LENGUAJE ENSAMBLADOR BÁSICO 00110111 _+ ____________ 1_ 00111000 Cuando realizamos la suma en una operacion en complemento a dos.). Extension del signo En ensamblador.1. Esto le permite al compilador determinar las instrucciones correctas a usar con el dato. Por ejemplo. todos los datos tienen un tamanño determinado. Sumar 2 bytes siempre produce como resultado un byte (tal como sumar 2 palabras produce otra palabra. considere el cero como un nímero en complemento a dos de un byte (00000000). 32. Esto hace la aritmetica de complemento a dos mas simple que los metodos anteriores. Si el valor FF es considerado para representar -1 o +255 depende del programador. se pueden representar los nímeros con signo desde -32. la suma del bit del extremo izquierdo puede producir un carry. Los nímeros de 32 bits en complemento a dos estan en el rango de -2 mil millones a +2 mil millones aproximadamente. Si se usan 16 bits.32 CAPITULO 2. El lenguaje C define tipos de entero con y sin signo (Signed. Reducir el tamaño es facil. 2. Así en la notacion de complemento a dos existe solo un cero.2. Usando la notacián en complemento a dos un byte con signo se puede usar para representar los nímeros desde -128 hasta +127. o palabra doble). unisigned).768 hasta 32. Calcular el complemento a 2 produce la suma: 11111111 + 1 c 00000000 donde c representa un carry (luego se mostrará como detectar este carry. pero no se almacena en el resultado). etc. La CPU no tiene ni idea que supuesta representacián tiene un byte en particular (palabra. Esta propiedad es importante para la notacián en complemento a dos.

2. Este bit serí el nuevo bit de signos del valor mas pequeño. Es importante que sea el bit del signo original. Sin embargo. si es un byte con signo (-1 en decimal). Un ejemplo trivial: mov ax. entonces la palabra debería ser FFFF. Considere números con signo. Si se extiende a una palabra. sin embargo. ax = 52 (almacenado en16 bits) . Este metodo trabaja con números con o sin signo. si el número no se puede representar correctamente en el tamaño mas pequeño la reduccion de tamaño no funcionar!. observe que ¡esto no es correcto si el valor en AX era sin signo! La regla para números sin signo es que todos los bits al ser quitados deben ser 0 para que la conversion sea correcta. La regla para los números con signo es que los bits que sean quitados deben ser o todos 1o todos 0. Considere el byte hex FF. entonces code CL sería FFh (-1 como byte). 0034h mov cl.1. Aumentando el tamaño de los datos Incrementar el tamaño de los datos es mas complicado que disminuirlo. al . cl = los 8-bits inferiores de ax Claro esta. . ¿Que valor debería tener la palabra? Depende de como se interprete la palabra. Por ejemplo si AX era 0134h (o 308 en decimal) entonces el cúdigo anterior almacenaría en CL 34h. entonces la palabra debería ser 00FF. TRABAJANDO CON ENTEROS 33 Para reducir el tamaño del dato simplemente quite los bits mas significativos del dato. Si FF es un byte sin signo (255 en decimal). si AX era FFFFh (-1 como palabra). Ademas el primer bit no se debe quitar pero debe tener el mismo valor que los bits quitados.

para extender el byte en AL a una palabra sin signo en AX: mov ah. Por ejemplo. Así FF se convierte en 00FF. La instruccion CWDE (Convert Word to Double word Extended) extiende AX en EAX. Si el nímero con signo 5A (90 en decimal) fue extendido. la in. La otra restriccion es que el destino debe ser mayor que la fuente (la mayoría de instrucciones requieren que la fuente y el destino sean del mismo tamaño). Los operandos son implícitos. La notacion DX:AX significa interpretar los registros DX y AX como un registro de 32 bits con los 16 bits superiores almacenados en DX y los 16 bits inferiores en AX.34 CAPITULO 2. no es posible usar la instruccion MOV para convertir la palabra sin signo en AX a una palabra doble en EAX. Existen varias instrucciones que suministra el 80386 para la extension de los nímeros. para extender un nímero sin signo. EL 8086 suministra varias instrucciones para extender números con signo. 0 . ¿Por que no? No hay manera de referirse a los 16 bits superiores de EAX con una instruccion MOV. no hay una manera facil de usar la instrucción MOV. El 80386 resuelve este problema con una nueva instruccion MOVZX . El destino (primer operando) debe ser un registro de 16 o 32 bits. ax extiende ax en eax extiende al en eax extiende al en ax extiende ax en ebx Para números con signo. . al ebx. los nuevos bits deben ser todos unos. al ax. (Recuerde que el 8086 no tenía ningún registro de 32 bits). La fuente (segundo operando) puede ser un registro de 8 o 16 bits o un byte o una palabra en memoria. Finalmente. el resultado sería 005A. LENGUAJE ENSAMBLADOR BÁSICO En general. La instruccion CBW (Convert Byte to Word) extiende el registro AL en AX.struccion MOVSX trabaja como MOVZX excepto que usa las reglas para números con signo. Esta instruccián tiene dos operandos. Ya que el bit de signo de FF es 1. uno hace cero todos los bits nuevos del nímero extendido. para producir FFFF. La instrucciún CWD (Convert Word to Double Word) extiende AX en DX:AX. Recuerde que el computador no conoce si un nímero esta con o sin signo. El 80386 añadio varias instrucciones nuevas. Sin embargo. cero los 8-bits superiores Sin embargo. La instruccion CDQ (Convert Double word to Quad word) extiende EAX en EDX:EAX (¡64 bits!). Algunos ejemplos: movzx movzx movzx movzx eax. para extender un nímero con signo uno debe extender el bit de signo. Esto significa que los nuevos bits se convierten en copias del bit de signo. ax eax. Es responsabilidad del programador usar la instruccion adecuada. uno puede simplemente colocar ceros en los bits superiores usando una instruccion MOV. Para nímeros sin signo.

Sin embargo hay un valor que puede retornar que no es un caríacter. C truncará los bits de mayor peso para que el entero quepa en el caracter. Considere el es responsabilidad de ca. Este es un macro que normalmente se define como —1. TRABAJANDO CON ENTEROS 35 unsigned char uchar = 0xFF.2. con esto directamente.1.2. El ínico problema es que los nímeros (en hex) 000000FF y FFFFFFFF ambos se truncarán al byte FF.1. pero este valor se almacena en un char.. Ya que EOF es un valor int. . v el tipo esta explícitamente ° ° ' definido en la Figura 2. la variable code a se extiendo usando da comp:ladordecidir est°. /* a = 255 (0x000000FF) */ int b = (int) schar. las reglas para valores sin signo (usando MOVZX). .2 es que fgetc() retorna un entero. Considere el codigo de la Figura 2.1: char ch. Las variables en C char es con signo o no. Así el ciclo while no puede distinguir entre el byte FF y el fin de archivo (EOF).codigo de la Figura 2. . El prototipo de fgetc( ) es: fgetc()is: int fgetc( FILE * ).1. En la línea 3.. l a s reglas con signo para b (usando MOVSX). while( (ch = fgetc(fp)) != EOF ) { /* hace algo con ch */ } Figura 2. El problema principal con el programa de la Figura 2. Uno podría preguntar ¿Por que la funciín retorna un int siendo que lee caracteres? La razon es que normalmente retorna un char (extendido a un valor entero usando la extension cero). Hay un error muy comín en la programaciín en C que tiene que ver . EOF. 10 ch serí extendido a un int de modo que los dos valores comparados sean 10Es un concepto erróneo pensar que los archivos tienen un carácter EOF al final. se pueden declarar como int signed o unsigned (int es signed) . /* b = —1 (0xFFFFFFFF) */ Figura 2. signed char schar = 0xFF. ¡Esto no es verdad! . Así fgetc() o retorna un carácter extendido a entero (que es como 000000xx en hex) o EOF (que es FFFFFFF en hex). pero en la línea 4 se usan Esta es la razón por la cual . int a = (int) uchar. .2: Aplicación a la programación en C ANSI C no define si el tipo Extender enteros con y sin signo tambien ocurre en C. Lo que sucede exactamente en este caso. depende de si el char es con signo o sin signo ¿por que? Porque en la línea 2 ch es comparada con EOF.

Esto se compara como igual y el bucle finaliza. ¡el bucle nunca terminará!. 11 como se muestra en la Figura 2. Cuando esto se hace no se truncara o extenderá en la línea 2. Una de las grandes ventajas del complemento a 2 es que las reglas para la adicion y sustraccion son exactamente las mismas que para los enteros sin signo.2: instrucciones imul 11La razón para este requerimiento se mostrara luego. Así. LENGUAJE ENSAMBLADOR BÁSICO del mismo tamaño. Dos de los bits en el registro FLAGS. ya que el byte FF puede haber sido leído de un archivo. FF se extenderá a 000000FF. pero no es parte de la respuesta. La solucion a este problema es definir la variable ch como un int no como un char. la instruccion add efectúa una suma y la in. La bandera de carry se fija si hay carry en el bit mas significativo de una suma o un préstamo en el bit mas significativo de una resta.36 CAPÍTULO 2. 002C 44 + FFFF + (-1) 002B 43“ Hay un carry generado. Si char es con signo se extenderá a FFFFFFFF. que estas instrucciones fijan son las banderas de desborde y carry. Así. La bandera de desborde se fija si el resultado verdadero de la operacion es muy grande para caber en el destino para aritmetica con signo. .1. el se puede usar para detectar un desborde para aritmetica sin signo. 2. donde si la variable es con signo o sin signo es muy importante. Sin embargo.3. dest source1 source2 Action reg/mem8 AX = AL*source1 reg/mem16 DX:AX = AX*source1 reg/mem32 EDX:EAX = EAX*source1 reg16 reg/mem16 dest *= source1 reg32 reg/mem32 dest *= source1 reg16 immed8 dest *= immed8 reg32 immed8 dest *= immed8 reg16 immed16 dest *= immed16 reg32 immed32 dest *= immed32 reg16 reg/mem16 immed8 dest = source1*source2 reg32 reg/mem32 immed8 dest = source1*source2 reg16 reg/mem16 immed16 dest = source1*source2 reg32 reg/mem32 immed32 dest = source1*source2 Cuadro 2. Aritmetica de complemento a dos Como se vio al principio.1. Esto se compara con EOF (FFFFFFFF) y encontrará que no es igual. Así add y sub se pueden usar con enteros con o sin signo. Los usos de la bandera de carry para aritmetica con signo se verán dentro de poco. Si char es unsigned. Dentro del bucle es seguro truncar el valor ya que ahí ch debe ser un simple byte.struccion sub efectúa una resta. el bucle podría terminar prematuramente.

2.1. TRABAJANDO CON ENTEROS

37

Hay dos instrucciones diferentes para multiplicar y dividir. Primero para multiplicar use la instrucción MUL o IMUL. La instrucción MUL se emplea para multiplicar nómeros sin signo e IMUL se usa para multiplicar enteros con signo. ¿Por que se necesitan dos instrucciones diferentes? Las reglas para la multiplicacioón son diferentes para nuómeros en complemento a dos con signo o sin signo. ¿Como así? Considere la multiplicacion del byte FF con sí mismo dando como resultado una palabra. Usando una multiplicacion sin signo es 255 veces 255 o 65025 (o FE01 en hex). Usando la multiplicacion con signo es —1 veces —1 (o 0001 en hex). Hay varias formas de las instrucciones de multiplicación. La mas antigua es: mul source

source es un registro o una referencia a memoria. No puede ser un valor inmediato.
Exactamente que multiplicacion se realiza depende del tamaño del operando source. Si el operando es de un byte, este es multiplicado por el byte del registro AL y el resultado se almacena en los 16 bits de AX. Si la fuente es de 16 bits, se multiplica por la palabra en AX y el resultado de 32 bits se almacena en DX:AX. Si la fuente es de 32 bits este se multiplica por EAX y el resultado de 64 bits se almacena en EDX:EAX. La instruccion IMUL tiene la misma forma de MUL, pero tambien tiene algunos otros formatos. Hay dos y tres formas de operandos. imul dest, sourcel imul dest, sourcel, source2 La Tabla 2.2 muestra las posibles combinaciones. Los dos operadores para la divisiún son DIV e IDIV. Ellas efectúan la divisiún sin signo y con signo respectivamente. El formato general es:

div source

Si la fuente es de 8 bits, entonces AX es dividido por el operando. El cociente se almacena en AL y el residuo en ah. Si la fuente es de 16 bits, entonces DX:AX se divide por el operando. El cociente se almacena en AX y el residuo en DX. Si la fuente es de 32 bits, entonces EDX:EAX se divide por el operando y el cociente se almacena en EAX y el residuo en EDX. La in- struccion IDIV trabaja de la misma manera. No hay instrucciones especiales IDIV como las especiales en IMUL. Si el cociente es muy grande para caber en el registro o el divisor es cero, el programa se interrumpe y termina. Un error muy común es olvidar iniciar DX o EDX antes de la divisiún.

38

CAPÍTULO 2. LENGUAJE ENSAMBLADOR BÁSICO

La instruccion NEG niega su operando calculando su complemento a dos. El operando puede ser cualquier registro de 8, 16 o 32 bits o un lugar de memoria.

2.1.4.

Programa de ejemplo

math.asm

2.1. TRABAJANDO CON ENTEROS
%include "asm_io.inc" segment .data ; Cadenas de salida prompt db "Digite un número: ", 0 square_msg db "La entrada al cuadrado es ", 0 cube_msg db "La entrada al cubo es ", 0 cube25_msg db "La entrada al cubo 25 veces es ", 0 quot_msg db "El cociente del cubo/100 es ", 0 rem_msg db "El residuo del cube/100 es ", 0 neg_msg db "La negación del residuo es ", 0 segment .bss input resd 1 segment .text global enter pusha mov call call mov imul mov call mov call call mov imul mov call mov call call imul mov call mov call

39

_asm_main _asm_main: 0,0 ; rutina de inicio

eax, prompt print_string read_int [input], eax eax mov ebx, eax eax, square_msg print_string eax, ebx print_int print_nl ebx, eax ebx, [input] eax, cube_msg print_string eax, ebx print_int print_nl ecx, ebx, 25 eax, cube25_msg print_string eax, ecx print_int call ; edx:eax = eax * eax ; guarda la respuesta en ebx

; ebx *= [input]

; ecx = ebx*25

print_nl

eax, ebx

mov

suma los 32-bits inferiores . Aritmetica de precisión extendida El lenguaje ensamblador tambien suministra instrucciones que le permitan a uno hacer operaciones de suma y resta de nímeros mas grandes que palabras dobles. LENGUAJE ENSAMBLADOR BÁSICO __________________________________ math.1. ecx adc edx. En lugar de ello usa el difamado goto y su uso inapropiado puede resultar en un ¡codigo spaghetTi! Sin embargo es posible escribir programas estructurados en ensamblador. que controlan el flujo de ejecucion. se puede usar un bucle (vea seccion 2. resta los 32-bits inferiores . El procedimiento bísico es diseñar la logica del programa usando las estructuras de control de alto nivel y traducir el disenño en lenguaje ensamblador apropiado (parecido a lo que haría el compilador). El siguiente codigo resta EBX:ECX de EDX:EAX 1 2 sub eax. 2. suma los 32-bits superiores y el carry de la suma anterior La resta es muy similar.2). Estructuras de control Los lenguajes de alto nivel suministran estructuras del alto nivel (instrucciones if while). Para el bucle suma sería conveniente usar la instruccion ADC para cada iteracioín (en todas menos la primera). Las instrucciones ADC y SBB usan esta informacion en la bandera de carry. resta los 32-bits superiores y el préstamo Para nímeros realmente grandes. ebx . ecx sbb edx. ebx . Esto se puede hacer usando la instrucciín CLC (CLear Carry) antes que comience el bucle para iniciar la bandera de carry a cero.bandera de flag .40 CAPÍTULO 2.2.operando2 ¿Como se usan? Considere la suma de enteros de 64 bits en EDX:EAX y EBX:ECX .5. La instruccioín ADC hace la siguiente operacioín: operandol = operandol + bandera de carry + operando2 La instrucciín SBB realiza: operandol = operandol . El lenguaje ensamblador no suministra estas complejas estructuras de control. La misma idea se puede usar tambien para la resta. El siguiente cídigo podía almacenar la suma en EDX:EAX 1 2 add eax. Si la bandera de carry es cero no hay diferencia entre ADD y ADC. Como se vio antes las instrucciones ADD y SUB modifican la bandera de carry si se ha generado un carry o un príestamo respectivamente. Esta informaciín almacenada en la bandera de carry se puede usar para sumar o restar nímeros grandes dividiendo la operacion en piezas pequeñas de palabras dobles (o menores).asm ___________________________________ 2.1.2. Comparaciones . 2.

ZF es cero y SF = OF.. entonces ZF se borra y CF tambien (no hay préstamo). Instrucciones de ramificación Las instrucciones de ramificacián pueden transferir la ejecucion de un programa a un punto arbitrario. atrno Así SF = OF = 0 signo). Si vleft = vright. La bandera cero se fija si el resultado de la resta es cero. solo se puede mover arriba o . La si vleft > vrigh-t? Si no bandera de desborde se fija si el resultado de una operacion se desborda (o hay desb°rde. Asé SF = OF = 1. 2. Una ram. El registro FLAGS se fija basado en la diferencia de los dos operandos de la instruccion CMP. Para enteros sin signos hay dos banderas (bits en el registro FLAGS) que son importante: cero (ZF) y carry (CF). Los operandos se restan y se fija el registro FLAGS basado en el resultado. de hecho seré negativo). La bandera carry se usa como bandera de préstamo para la resta. El 80x86 suministra la instruccion CMP para realizar comparaciones. la diferencia no No olvide que otras instrucciones tambien cambian el registro FLAGS. el resultado de una comparacion se almacenan en el registro FLAGS para usarlas luego. Considere una comparacián como: cmp vleft. Para enteros con signo. vright La resta de vleft . Es importante tener en cuenta que la instruccion inmediatamente despues de la instrucciún JMP nunca se ejecutará a menos que otra instruccion salte a ella. Su argumento normalmente es una etiqueta de cédigo a la instruc. Si la diferencia de CMP es cero. ESTRUCTURAS DE CONTROL 41 Las estructuras de control deciden que hacer basados en la comparacion de datos. pero el resultado no se almacena en ninguna parte. Si necesita el resultado use la instruccián SUB en lugar de la instruccián CMP. siempre hace el salto.2. tendrá el valor correcto (y no solo CMP. el control pasa a la siguiente instruccion. vleft = vright. entonces Ia underflows).cion a la cual se debe saltar. Hay dos tipos de ramificaciones: condicionales e incondicionales. Si una ramificacion condicional no hace el salto.vright se calcula y las banderas se fijan de acuerdo al resultado. La bandera de signo se fija si el resultado de una operacián es diferencia tendré d val°r negativo. x . Si vleft < vright. SHORT Este salto es de tamaño muy limitado. Hay variaciones de la instruccion de salto. El ensamblador o el encadenador reemplazara la etiqueta con la direccion correcta de la instruccion. Si vleft > vright. „ .2.ificacián incondicional es tal cual como goto. Si vleft > vright.2.„ . En otras palabras funcionan como goto. Una ramificacion condicional puede o no hacer el salto dependiendo de las banderas del registro FLAGS. La instruccion JMP (acránimo de jump) hace ramificaciones incondicionales. entonces ZF se borrará y CF se fijará (hay préstamo).2. En ensamblador. .. entonces ZF se fija (ZF=1) y CF se borra (CF=0). Esta es otra de las labores aburridas que realiza el ensamblador para hacer la vida del programador mas facil.. ZF Sin embargo. si hay un es cero y SF = OF. desborde. hay tres banderas que son importante: la bandera ¿Por qué hace SF = OF cero (ZF). Si vleft < vright. la ZF se fija ( tal como para los enteros sin correcto y debe ser no neo. la bandera de desborde (OF) y la bandera de signo (SF).

Para especificar un salto corto. Hay muchas instrucciones de ramificacion condicional diferentes. Los dos puntos no son parte del nombre. Ellas tambien toman la etiqueta como su operando. Uno usa dos bytes para el desplazamiento. NEAR Este salto es el tipo por defecto en las ramificaciones condicionales e incondicionales y se puede usar para saltar a cualquier lugar del segmento. El desplazamiento es cuantos bytes se mueve adelante o atrás. (El desplazamiento se añade a EIP). Este es una cosa muy rara para hacerla en el modo protegido del 386. La ventaja de este tipo es que usa menos memoria que otros. que le permite a uno moverse a cualquier lugar en el segmento de cúdigo. Esto le permite a uno moverse aproximadamente 32000 bytes arriba o abajo. Las etiquetas de codigo vílidas siguen las mismas reglas que las etiquetas de datos. El tipo de 4 bytes es el de defecto en el modo protegido del 386. else EBX = 2. Usa un byte con signo para almacenar el desplazamiento del salto.42 CAPÍTULO 2. Vea la Tabla 2. Las etiquetas de codigo esrán definidas para colocarlas en el segmento de codigo al frente de la instruccion sus etiquetas. Las mís sencillas solo ven una bandera en el registro FLAGS para determinar si salta o no.3 para una lista de estas instrucciones (PF es la bandera de paridad que indica si el número de unos en los 8 bit inferiores es par o impar). El siguiente pseudocúdigo: if ( EAX == 0 ) EBX = 1. . El tipo de 2 bytes se puede especificar colocando la palabra WORD antes de la etiqueta en la instruccion JMP. El otro tipo usa cuatro bytes para el desplazamiento. Dos puntos se colocan al final de la etiqueta en este punto de la definicion. Actualmente el 80386 soporta 2 tipos de saltos cercanos. FAR Este salto permite mover el control a otro segmento de cúdigo. use la palabra SHORT inmediatamente antes de la etiqueta en la instruccion JMP. LENGUAJE ENSAMBLADOR BÁSICO abajo 128 bytes en memoria.

Si EAX es mayor que o igual a 5. 5 signon elseblock thenblock thenblock ebx.3: Saltos condicionales simples Se podría escribir en ensamblador como: cmp eax. ZF debe estar fija o borrada y SF sera igual a OF. Aquí esta el codigo en ensamblador que prueba estas condiciones (asumiendo que EAX es con signo): cmp js jo jmP jo eax. 0 thenblock ebx. 2 next ebx.0 = 0 ) mov jmP thenblock: mov next: Las otras comparaciones no son fíaciles usando las ramificaciones condicionales de la Tabla 2. 1 establece las banderas (ZF se fija si si ZF es 1 salta a thenblock parte ELSE del if salta sobre la parte THEN del IF parte THEN del IF eax . else EBX = 2.3 Para ilustrar esto considere el siguiente pseudocodi. 2 salta a signon si salta a elseblock salta a thenblock salta a thenblock SF = 1 si OF = 1 y SF = 0 si SF = 0 y OF = 0 si SF = 1 y OF = 1 signon: elseblock: mov . LENGUAJE ENSAMBLADOR BÁSICO Cuadro 2.go: if ( EAX >= 5 ) EBX = 1.43 JZ JNZ JO JNO JS JNS JC JNC JP JNP salta solo si ZF es uno salta solo si ZF es cero salta solo si OF es uno salta solo si OF es cero salta solo si SF es uno salta solo si SF es cero salta solo si CF es uno salta solo si CF es cero salta solo si PF es uno salta solo si PF es cero CAPÍTULO 2.

Hay versiones con y sin signo para cada tipo. JNAE JBE. JNBE JAE.3. 5 thenblock ebx. Usando estas nuevas instrucciones de ramificaciún. LENGUAJE ENSAMBLADOR BÁSICO JE JNE JL. 2 next thenblock: ebx. JNL salta salta salta salta salta salta si vleft si vleft si vleft si vleft si vleft si vleft = vright = vright <vright < vright >vright > vright JE JNE JB. el 80x86 suministra otras instrucciones de ramificacion que hace este tipo de pruebas mucho mís fácil. Por ejemplo mire que JL (jump less than) y JNGE (jump not greater than or equal to). Afortunadamente. Las ramificaciones igual y no igual (JE y JNE) son identicas para enteros con y sin signo. Cada una de esas instrucciones tiene una etiqueta como único operando. . 1 next: 2. cmp jge mov jmp mov eax.4 muestra estas instrucciones. Son la misma instruccion porque: x < y not(x > y) Las ramificaciones sin signo usan A por above y B para below en lugar de L y G. 1 next: El cúdigo anterior es muy complicado. JNGE JLE.2. (De hecho JE y JNE son las mismas que JZ y JNZ respectivamente). JNB Unsigned salta si vleft salta si vleft salta si vleft salta si vleft salta si vleft salta si vleft = vright = vright <vright < vright >vright > vright Cuadro 2.44 Signed CAPITULO 2. La Tabla 2. JNLE JGE. el pseudocodigo de arriba puede ser traducido a ensamblador mucho mís fícil.4: Instrucciones de comparacion con y sin signo jmp next thenblock: mov ebx. Cada una de las otras instrucciones de ramificacion tienen dos sinonimos. JNG JG. Instrucciones de bucle El 80x86 suministra varias instrucciones para implementar bucles del tipo for. JNA JA.

salta Las dos íltimas instrucciones de bucle son ítiles para bucles de bísqueda secuencial. si ECX = 0. cédigo para bloque entonces jmp endif else_block: . LOOPZ Decrementa ECX (el registro FLAGS no se modifica). cédigo para bloque else endif: .3. LOOPNZ Decrementa ECX (FLAGS sin cambio). salta a la etiqueta 45 LOOPE.1. eax es sum . podría implementarse como: . i --------. Traducir estructuras de control estándares Esta seccion muestra como las estructuras de control estíndares de los lenguajes de alto nivel se pueden implementar en lenguaje ensamblador. salta LOOPNE. si ECX = 0 y ZF = 1. El siguiente pseudocodigo: sum = 0. for ( i =10. instrucciones if El siguiente pseudocoídigo: if ( ocondicin ) bloque entonces. else bloque else. ESTRUCTURAS DE CONTROL LOOP Decrementa ECX.) sum += i. 0 mov ecx. si ECX = 0 y ZF = 0. code to set FLAGS jxx else_block .2. i >0. 10 loop_start: add eax. ecx loop loop_start . 2. Podría ser traducido a ensamblador como: mov eax.3. ecx es i 2.2. selecciona xx tal que salta si la condicién es falsa .

} while( Ocondicin ). Esto podría ser traducido en: do: .3. bucles do while El bucle do while se prueba al final del bucle: do { cuerpo del bucle. código para fijar FLAGS basado en la condición jxx do . /* el posible factor */ unsigned limit. bucles while El bucle while se prueba al inicio del bucle: while( Ocondicin ) { cuerpo del bucle. código para el bloque entonces endif: 2. selecciona xx tal que salte si xx es falso . If there is no else. code to set FLAGS jxx endif . selecciona xx tal que salta si la condición es falsa . TRADUCIR ESTRUCTURAS DE CONTROL ESTÁNDARES 46 Si no hay else.2.3. cuerpo del bucle . seleccionar xx tal que salte si es verdadero unsigned guess. /* encontrar primos hasta este valor */ .3.3. then the else_block branch can be replaced by a branch to endif.2. código que fija FLAGS basado en la condición de jxx endwhile . } Esto podría traducirse en: while: . /* La conjetura actual para el primo */ unsigned factor. entonces el else_block ramificado puede ser reemplazado por una rama a endif. . body of loop jmp while endwhile: 2.

scanf(” %u”. 0 segment . .inc" segment .bss Limit Guess 1 2 3 4 4 5 6 8 resd 1 resd 1 . El metodo búsico de este programa es encontrar los factores de todos los números impares 12 bajo un límite dado. No hay fórmula para hacer esto.3 muestra el algoritmo basico escrito en C. halle primos hasta este límite .4. la conjetura actual para el primo 122 es el único número par. Aca estú la versión en ensamblador _________________________________ prime. EJEMPLO: HALLAR NÚMEROS PRIMOS 47 printf (”Find primes up to: ”).2.4. /* trata los dos primeros primos */ printf (”3\n”). /* óslo busca en los unmeros impares */ } Figura 2. printf (”2\n”).asm ___________________________________ %include "asm_io. Si no se puede encontrar un factor para un número impar. es primo La Figura 2.data Message db "Halle primos hasta: ". if ( guess % factor != 0 ) printf (” %d\n”. Ejemplo: hallar números primos Esta sección trata de un programa que encuentra números primos. Recuerde que un número primo es divisible solo por 1 y por sí mismo. &limit).3: 2. while ( factor * factor < guess && guess % factor != 0 ) factor += 2. /* conjetura inicial */ while ( guess <= limit ) { /* busca un factor */ factor = 3. /* como caso especial */ guess = 5. guess += 2. guess).

use jnbe ya que los números son sin signo ebx. 3 . scanf("%u". . printf("2\n"). Guess = 5. factor += 2.[Guess] edx. rutina de inicio . if !(factor*factor < guess) eax.ebx eax end_while_factor eax. printf("%u\n") jmp while_factor end_while_factor: je end_if mov eax.[Guess] eax.0 CAPITULO 2. . printf("3\n"). eax. edx = edx:eax % ebx . [Guess] end_while_factor eax. Message print_string read_int [Limit]. 3 print_int print_nl dword [Guess]. . 0 end while factor . & limit ebx. 2 print_int print_nl eax.48 segment . edx:eax = eax*eax . LENGUAJE ENSAMBLADOR BÁSICO ).[Guess] je add . if !(guess % factor != 0) . eax eax. . [Limit] end_while_lim it . Si la respuesta no cabe en eax . if !(guess % factor != 0) . . while ( Guess <= Limit ) . 5 eax.text global _asm_main: enter pusha mov call call mov mov call call mov call call mov while_limit: mov cmp jnbe mov while_factor: mov mul jo cmp jnb mov mov div cmp asm main 0.0 ebx edx. ebx is factor = 3.2 .

0 leave ret _________________________________ prime . guess += 2 49 retorna a C asm . 2 jmp while_limit end_while_limit: popa mov eax. EJEMPLO: HALLAR NÚMEROS PRIMOS call print_int call print_nl end_if: add dword [Guess].4.2.

Los desplazamientos pueden ser hacia la izquierda (hacia el bit mas significativo) o hacia la derecha (el bit menos significativo). = 4123H. ax. Operaciones de desplazamientos El lenguaje ensamblador le permite al programador manipular bits individuales de los datos. 3. Desplaza de una manera muy directa. Estas instrucciones permiten desplazar cualquier nuúmero de posiciones. A continuacion. La Figura 3. El último bit desplazado se almacena en la bandera de carry. Una operación común es llamada un desplazamiento.1. algunos ejemplos: i 2 3 4 mov shl shr shr ax. Original l l l o l o Left shifted l l o l o l Right shifted o l l l o l Figura 3.1. Se usan las instrucciones SHL y SHR para realizar los desplazamientos a la izquierda y derecha respectivamente.1. CF CF =0 =1 50 . ax.1: Desplazamientos lógicos l o o o o l Observe que los nuevos bits son siempre cero.Capítulo 3 Operaciones con bits 3. ax. desplaza un bit . El número de posiciones puede ser o una constante o puede estar almacenado en el registro CL. 0C123H 1 1 1 . derecha. CF = 2091H.1 muestra un ejemplo del desplazamiento de un byte. ax derecha. ax ax = 8246H. Desplazamientos lógicos Un desplazamiento logico es el tipo mas simple de desplazamiento. desplaza un bit . Una operación de desplazamiento mueve la posición de los bits de algún dato. desplaza un bit a la a la a la izquierda.

ax = 048CH. Por ejemplo para duplicar el numero binario 10112 (o 11 en decimal). Se ensambla con el mismo código de maquina que SHL. si un byte se desplaza con esta instruccion. SAL (Shift aritmetic left). OPERACIONES DE DESPLAZAMIENTOS 51 5 6 7 8 mov shl mov shr 3. CF = 1 Uso de los desplazamientos Los usos móas comunes de las operaciones de desplazamientos son las multiplicaciones y divisiones rápidas. Como el bit de signo no se cambia por el desplazamiento. los nuevos bits son tambien 1). desplaza dos bit a la izquierda. ax. Desplazamientos aritméticos Estos desplazamientos esrán diseñados para permitir que nómeros con signo se puedan multiplicar y dividir rápidamente por potencias de 2. Como las otras instrucciones de desplazamiento. . Considere el valor bytes FFFF (-1).2. 0C123H ax.Esta es una instruccion nueva que no desplaza el bit de signo (el bit mas significativo) de su operando. desplaza tres bit a la derecha.1. El cociente de una división por una potencia de dos es el resultado de un desplazamiento a la derecha.1. los desplazamientos lógicos se pueden usar para multiplicar y dividir valores sin signo. ax = 0091H. 3 ax. sólo los 7 bits inferiores se desplazan. Lo mismo se aplica para las potencias de dos en binario. el uóltimo bit que sale se almacena en la bandera de carry. Esta instrucción es solo sinónimo para SHL. 3. para dividir por 8 desplace 3 lugares a la derecha etc.1. Ellos aseguran que el bit de signo se trate correctamente. CF = . Ellos no funcionan para valores con signo. el resultado seróa correcto. Para dividir por 2 solo haga un desplazamiento a la derecha. Se pueden usar otro tipo de desplazamientos para valores con signo. Actualmente. al desplazar una vez a la izquierda obtenemos 101102 (o 22). para dividir por 4 (2 2) desplace los bits 2 lugares. cl .767. Los otros bits se desplazan como es normal excepto que los bits nuevos que entran por la derecha son copias del bit de signo (esto es. Las instrucciones de desplazamiento son muy elementales y son mucho mós rápidas que las instrucciones correspondientes MUL y DIV .3. Si este se desplaza logicamente a la derecha una vez el resultado es 7FFF que es +32. SAR SAR Shift Arithmetic Right . solo se desplazan los dígitos. 2 cl. Recuerde que en el sistema decimal la multiplicación y división por una potencia de 10 es sencilla.3. Asó. si el bit de signo es 1.

ax. CF = 1 ax = 0123H. mov clc rcl rcl rcl rcr ax. mov rol rol rol ror ror ax. Así. el dato es tratado como si fuera una estructura circular. ax. ax. si el registro AX rota con estas instrucciones. CF = 0 ax = 8246H. ax. Las dos rotaciones mas simples son ROL y ROR . ax = 8247H. bl contendró el número de bits prendidos . ax. ax. estos desplazamientos dejan una copia del ultimo bit rotado en la bandera de carry. CF = 1 ax = 091BH.1. ecx es el bucle contador . 2. CF = 1 ax = 048DH. OPERACIONES CON BITS ax = 8246H. Por ejemplo. rcr Aplicacion simple A continuacion esta un fragmento de codigo que cuenta el nímero de bits que están “encendidos” (1) en el registro EAX. CF = CF = CF = CF = CF = 1 1 0 1 1 Hay dos instrucciones de rotacion adicionales que desplazan los bits en el dato y la bandera de carry llamadas RCL y RCR . 1. ax = 048FH. los 17 bits se desplazan y la bandera de carry se rota. 1 1 1 2 1 borra la bandera de carry (CF ax = 8246H. 1 1 2 CAPÍTULO 3.5. Desplazamientos de rotación Los desplazamientos de rotacion trabajan como los desplazamientos logi. CF = 0 3. CF = 0 = 0) 3. 0C123H ax.cos excepto que los bits perdidos en un extremo del dato se desplazan al otro lado. 0 mov ecx. 32 . Tal como los otros desplazamientos. que hacen rotaciones a la izquierda y a la derecha respectivamente. ax. CF = 1 ax = C123H. CF = 1 ax = 048CH.52 mov sal sal sar ax. ax = 091EH. ax. mov bl. 1. 0C123H ax.4. ax.1. ax = C123H. ax = 8247H. 1. ax. ax. 0C123H 1.

2. 0C123H and ax. 3.1.1. 3. la línea 4 debería ser reemplazada con rol eax. Por ejemplo. OPERACIONES DE DESPLAZAMIENTOS X 0 0 1 1 53 Y 0 1 0 1 X AND Y 0 0 0 1 Cuadro 3. si el contenido de AL y BL se les opera con AND. count_loop: shl jnc inc skip_inc: loop skip_inc bl 1 desplaza los bits en la bandera de carry si CF ==0.1: La operación AND 10 10 10 10 AND 110 0 10 0 1 1 ^“0 0 1 0 0 0 Figura 3. 82F6H .1 Los procesadores tienen estas operaciones como instrucciones que actuúan independientemente en todos los bits del dato en paralelo.3. Sigue un codigo de ejemplo: i 2 mov ax.2.2: ANDdo un byte eax. va a skip_inc count_loop El codigo anterior destruye el valor original de EAX (EAX es cero al final del bucle). OR. si no el resultado es cero como muestra la Tabla 3. ax = 8022H .1. la operación se aplica a cada uno de los 8 pares de bits correspondientes en los dos registros como muestra la Figura 3. La operacion AND El resultado del AND de dos bits es 1 solo si ambos bits son 1. Una tabla de verdad muestra el resultado de cada operacion por cada posible valor de los operandos.2 . Si uno desea conservar el valor de EAX. XOR y NOT. Operaciones booleanas entre bits Hay cuatro operadores booleanos basicos AND.

Sigue un cádigo de ejemplo: mov ax. La operación NOT La operacion NOT es unaria (actía sobre un solo operando. Diferente la instruccion NOT no .3. Sigue un codigo de ejemplo: mov ax.2.3.2. OPERACIONES BOOLEANAS ENTRE BITS X 0 0 1 1 54 Y 0 1 0 1 X OR Y 0 1 1 1 Cuadro 3.2.4. 0C123H xor ax. sino el resultado es 1 como muestra la tabla 3.2 .3: La operacián XOR 3. si no el resultado es 1 como se muestra en la Tabla 3. complemento a 1. 0C123H not ax ax = 3EDCH Observe que NOT halla el a las otras operaciones entre bits.4. 0C123H or ax. El NOT de un bit es el valor opuesto del bit como se muestra en la Tabla 3. 0E831H ax = E933H 3. no como las operaciones binarias como AND).2. La operación XOR El O exclusivo entre 2 bits es 0 si y solo si ambos bits son iguales.3. Sigue un cádigo de ejemplo: mov ax.2.2: La operación OR X 0 0 1 1 Y 0 1 0 1 X XOR Y 0 1 1 0 Cuadro 3. cambian ninguno de los bits en el registro FLAGS. 0E831H ax = 2912H 3. La operación OR El O inclusivo entre dos bits es 0 solo si ambos bits son 0.

ax. Este operando es a menudo llamado máscara Complementa el bit i XOR el nómero con 2* Cuadro 3. si el resultado fuera cero. ax = invierte nibbles.3. ZF se fijaría.6. ax.5 muestra los 3 usos mas comunes de estas operaciones.5.5: Usos de las operaciones booleanas 3. A continuacion un fragmento de codigo que encuentra el cociente y el residuo de la division de 100 por 16. i 2 3 4 5 6 7 8 mov or and xor or and xor xor ax. AND el nómero con una mascara igual a 2* — 1. EVITANDO SALTOS CONDICIONALES X 0 1 NOT X 1 0 55 Cuadro 3.4: La operacion NOT Prende el bit i OR the number with 2* (which is the binary number with just bit i on) Apaga el bit i AND el nómero binario con solo el bit i apa gado . ax = C12BH = C10BH ax = 410BH ax = 4F0BH 4F00H ax = BF0FH ax = 40F0H La operación AND se puede usar tambien para hallar el residuo de una division por una potencia de dos. Usos de las operaciones con bits Las operaciones con bits son muy ótiles para manipular bits individuales sin modificar los otros bits. ax.2. ax. ax. pero no almacena el resultado. apaga nibble. La Tabla 3. Para encontrar el residuo de una division por 2*. Solo fija las banderas del registro FLAGS dependiendo del resultado que fuera (muy parecido a lo que hace la instrucción CMP con la resta que solo fija las banderas). ax invierte el bit 31. Son solo estos bits los que contienen el residuo. ax. Sigue un ejemplo de cóomo implementar estas ideas. 3. El resultado de la AND conservara estos bits y dejara cero los otros. La instrucción TEST La instruccion TEST realiza una operacion AND. Esta mascara contendrá unos desde el bit 0 hasta el bit i — 1.2. . prende el nibble. Por ejemplo. ax. apaga el bit 5.3. 0C123H 8 0FFDFH 8000H 0F00H 0FFF0H 0F00FH 0FFFFH prende el bit 3. complemento a uno .

1 = 15 or F ebx = residuo = 4 Usando el registro CL es posible modificar arbitrariamente bits. eax 100 = 64H múcara = 16 . . cl eax. El número del bit a pender se almacena en BH. This instruction is used because its machine code is smaller than the corresponding MOV instruction. first build the number to AND with mov cl. bl contendrá el número de bits prendidos ecx es el bucle contador se desplaza el bit en lá bandera de carry añade solo la bandera de carry a bl count_loop Figura 3. Las instrucciones . 32 eax. 100 ebx. eax eax = 0 A number XOR’ed with itself always results in zero.56 mov mov count_loop: shl adc loop CAPÍTULO 3. mov mov shl or cl. .3 : Contando bits con ADC mov mov and eax. r . 1 ebx. Es común ver esta instrucción en un programa 80X86. first build the number to OR with se desplaza a la derecha cl veces prende el bit . cl ebx eax. OPERACIONES CON BITS bl. Una tecnica común se conoce como ejecución especulativa. 1 ebx. bh ebx.3. Apaga un bit es solo un poco más difícil. Evitando saltos condicionales Los procesadores modernos usan técnicas sofisticadas para ejecutar el código tan rápido como sea posible. 0 ecx. 0000000FH . ebx. El siguiente es un ejemplo que fija (prende) un bit arbitrario en EAX. 0 . . . 1 bl. ebx . Esta técnica usa las capacidades de procesamiento paralelo de la CPU para ejecutar múltiples instrucciones a la vez. xor eax. bh mov shl not and ebx. 3. ebx se desplaza a la derecha cl veces invierte los bits apaga el bit un bit arbitrario es dejado como ejercicio al El código para complementar lector.

se ejecutara de instrucciones diferentes que si no se toma. Por ejemplo. El procesador trata de predecir si ocurrirá la ramificación. The example program below shows how the maximum can be found without any branches. The standard approach to solving this problem would be to use a CMP and use a conditional branch to act on which value was larger.0 message2 db "Digite otro numero: ". setz al . Los caracteres luego de SET son los mismos caracteres usados en saltos condicionales.data messagel db "Digite un numero: ". es evitar usar ramificaciones condicionales cuando es posible. El codigo de ejemplo en 3. AL = 1 if Z flag is set. considere el problema de encontrar el mayor de dos valores.1.3. La aproximacion normal para resolver este problema sería el uso de CMP y usar un salto condicional y proceder con el valor que fue múas grande. Una manera de evitar este problema. Archivo: max.bss 1 2 3 4 4 5 6 8 7 . La figura 3. else 0 Usando estas instrucciones.asm "/include "asm_io. los bits “encendidos” del registro EAX usa una ramificaciún para saltarse la instrucciún INC. For example. En el ejemplo anterior. basado en el estudio del registro FLAGS. uno puede desarrollar algunas túecnicas ingeniosas que calculan valores sin ramificaciones. Esta instruccion fija el valor de un registro byte o un lugar de memoria a cero. 0 segment . Por ejemplo.3. consider the problem of finding the maximum of two values. 0 message3 db "El mayor numero es: ". El procesador. Si la condicion correspondiente de SETxx es verdadero.3 muestra cúmo se puede quitar la ramificacion usando la instruccion ADC para sumar el bit de carry directamente. El programa de ejemplo de abajo muestra cúmo se puede encontrar el mayor sin ninguna ramificacioún. en general.inc" segment . no sabe si se realiza o no la ramificacion.5 muestra un programa muy simple donde uno podría hacer esto. si es falso se almacena cero. EVITANDO SALTOS CONDICIONALES 57 condicionales tienen un problema con esta idea.Si se toma. Las instrucciones SETxx suministran una manera para suprimir ramificaciones en ciertos casos. el resultado almacenado es uno. . Si la predicción fue errónea el procesador ha perdido su tiempo ejecutando un cúdigo erróneo.

ebx ecx. ebx . message3 print_string eax. ebx eax. Si la segunda entrada es mayor o 0 en otro caso. EVITANDO SALTOS CONDICIONALES input1 resd 1 .3. eax setup routine imprime el primer mensaje ingresa el primer numero imprime el segundo mensaje ingresa el segundo numero ebx = 0 compara el segundo y el primer número ebx = (input2 > input1) ? ebx = (input2 > input1) ? ecx = (input2 > input1) ? ecx = (input2 > input1) ? ebx = (input2 > input1) ? ebx = (input2 > input1) ? ecx = (input2 > input1) ? imprime los resultado 1: 0xFFFFFFFF : 0xFFFFFFFF : input2 : 0: 0: input2 : (en eax) retorna a C El truco es crear una mascara que se pueda usar para seleccionar el valor mayor. [input1] ecx. (Observe que se borro EBX primero). message1 print_string read_int [input1]. Esta es la máscara que se . 0 eax. primer número ingresado 58 segment . el resultado es la representacion en complemento a dos de -1 o 0xFFFFFFFF. esta no es la mascara deseada.text global _asm_main _asm_main: enter 0. La instruccion SETG en la línea 30 fija BL a 1.0 .3. la lónea requerida 31 usa la instruccion NEG en el registro EBX. Si EBX es 0 no hace nada. [input1] bl . ebx. Para crear la mascara que se r. eax ebx . sin embargo si EBX es 1. message2 print_string read_int ebx. ecx. pusha mov call call mov mov call call xor cmp setg neg mov and not and or mov call mov call call popa mov leave ret eax. ecx print_int print_nl eax. ebx eax.

Estos operadores toman 2 operandos. MANIPULANDO BITS EN C 59 necesita.4. Y la operación NOT se representa con el operador unario ~ . /* u = 0x0B20 (desplazamiento olgico) */ s = s >> 2.El operador << realiza desplazamientos a la izquierda y el operador >> hace desplazamientos a la derecha. Los desplazamientos son realizados por C con los operadores binarios << y >> .4.1.2. /* s = 0xFFF0 */ s = s"u. los valores son invertidos que cuando se usa la instruccion NEG. En el código de arriba. /* u = 0x0064 */ u = u | 0x0100. /* s = 0xFE94 */ u = u << 3.4. Si el valor es con signo (como int). short unsigned u. Como truco alternativo usar la instruccion DEC. /* se asume que short int es de 16 bits */ s = —1. /* s = OxFFFF (complemento a dos) */ u = 100. entonces se usa un desplazamiento aritmóetico. de nuevo el resultado seró 0 o 0xFFFFFFFF. 3. El de la derecha es el valor a desplazar y el de la izquierda es el nuómero de bits a desplazar. Usando las operaciones entre bits en C Los operadores entre bits se usan en C para los mismos propoósitos que en lenguaje ensamblador. Manipulando bits en C Las operacones entre bits de C A diferencia de algunos lenguajes de alto nivel C suministra operadores para operaciones entre bits. Sigue un ejemplo en C del uso de estos operadores: short int s. 3.4. se realiza un desplazamiento lógico. /* s = 0xFFA5 (desplazamiento éaritmtico) */ 3. Sin embargo. Si el valor a desplazar es un tipo sin signo. La operación OR se representa por el operador binario |. La operacion XOR se representa con el operador binario " . /* u = 0x0164 */ s = s & 0xFFF0.3. La operacioón AND se representa con el operador &13. El resto del codigo usa esta móscara para seleccionar la entrada correcta como el mayor. si NEG se reemplaza con un DEC. Ellos le permiten a uno manipular bits individuales 13¡Este operador es diferente del operador binario && y del unario &! .

Los otros permisos no son alterados Los otros permisos no se alteran. los sistemas POSIX mantienen los permisos de los archivos para 3 tipos diferentes de usuarios (un mejor nombre sería propietario). Por ejemplo. Una norma desarrollada por el IEEE basado en UNIX. Programming Interface Portable Operatting System Interface for Computer Enviroments.st-mode holds permission bits */ chmod(”foo”. & file_stats ). La funcion chmod se puede usar para establecer los permisos de un archivo.60 Macro CAPÍTULO 3. /* estructura usada por stat () */ stat(”foo”. Esta funcion toma dos parámetros. Muchos API14 (Como POSIX 15 y Win 32). La funcion POSIX stat se puede usar para encontrar los bits de permisos actuales de un archivo. el cádigo de abajo fija los permisos para permitir que el propietario del archivo leerlo y escribirlo. una cadena con el nombre del archivo sobre el que se va a actuar y un entero16 Con los bits apropiados para los permisos deseados. OPERACIONES CON BITS Meaning S_IRUSR el propietario puede leer el propietario S_IWUS puede escribir el propietario puede ejecutar R S_IXUSR S_IRGRP el grupo de propietario puede leer el grupo S_IWGR del propietario puede escribir el grupo del P propietario puede ejecutar S_IXGRP los otros pueden leer los otros pueden S_IROT H escribir los otros pueden ejecutar S_IWOT Cuadro 3. grupo y otros. SJRUSR | SJWUSR | SJRGRP ). tienen funciones que usan operandos que tienen datos codificados como bits. filestats .6). a los usuarios en e. Usada con la funcion chmod. grupo leer en archivo y que los otros no tengan acceso. Para cambiar los permisos de un archivo requiere que el programador de C manipule bits individuales. 16Actualmente un parámetro de tipo mode_t que es un typedef a un tipo integral.6: Macros POSIX para permisos de archivos H S_IXOT y se pueden usar para H multiplicaciones y divisiones rápidas. Un ejemplo que quita el acceso a la escritura a los otros y añade el acceso al propietario del archivo a la lectura. escribir o ejecutar un archivo. ( file_stats . De hecho. struct stat file_stats . 15Significa 14Aplication . chmod(”foo”. es posible modificar algunos de los permisos sin cambiar los otros. Por ejemplo. /* lee la 0informacin del archivo. POSIX define varios macros para ayudas (vea la tabla 3. A cada tipo de usuario se le puede conceder permisos para leer.st_mode & ~S_IWOTH) | S_IRUSR). un compilador de C inteligente usara desplazamientos automaticamente para multiplicaciones como X*=2.

los bytes se almacenarían como 12 34 56 78. Se puede descomponer en registros de un byte (AH y AL). Little endian almacena los bytes en el orden contrario (primero el menos significativo). ya que los bits individuales no se pueden direccionar directamente en la CPU o en la memoria. En la representacion little endian los bytes se almacenarían como 78 56 34 12. Los circuitos no estón en un orden particular en la CPU. El mismo argumento se aplica a los bits individuales dentro de un byte. luego el siguiente byte en peso y asó sucesivamente. Hay circuitos en la CPU que mantienen los valores de AH y AL. . considere la palabra doble 1234567816. no hay manera de conocer que orden ellos parece que conservaran internamente en la CPU. MANIPULANDO BITS EN C 61 3. Considere el registro de 2 bytes AX.5. En otras palabras los bits de más peso se almacenan primero. Esto significa que estos circuitos para AH no estón antes o despues que los circuitos para AL. Sin embargo. Como un ejemplo. Sin embargo. El lector recordaróa que lo endian se refiere al orden en que los bytes individuales se almacenan en memoria (no bits) de un elemento multibyte se almacena en memoria.3. no es difícil para la CPU hacer que almacene AH primero. En la repre. Almacena el byte mós significativo primero. Quiere decir que. El lector probablemente se preguntara asó mismo. el autor ha encontrad que este tema confunde a muchas personas. La respuesta es que la CPU no hace ningún trabajo extra para leer y escribir en la memoria usando el formato little endian. no hay realmente ninguón orden en los circuitos de la CPU (o la memoria). Big endian es el metodo mas directo. Los bits (y bytes) no esrán en un orden en particular en la CPU. Esta sección cubre el topico con mas detalle. ¿por que cualquier diseñador sensato usa la representacion little endian? ¿Eran los ingenieros de Intel sadicos para infligir esta confusa representación a multitud de programadores? Parecería que la CPU no tiene que hacer trabajo extra para almacenar los bytes hacia atrás en la memoria (e invertirlos cuando los lee de la memoria).sentacion big endian. O entiende que la CPU esta compuesta de muchos circuitos electrónicos que simplemente trabajan con bits. La familia de procesadores X86 usa la representación little endian.4. Representaciones Littel Endian y Big Endian El capótulo 1 introdujo el concepto de las representaciones big y little endian de datos multibyte.

La mayoría de las veces esto es importante cuando se transfiere datos binarios entre sistemas de computo diferente.3. Así. Por ejemplo la funcion tipos de representación y htonl() convierte una palabra doble ( o long int) del formato del host al de red. lo endian de la CPU no es importante. Cuando tener cuidado con Little and Big Endian Para la programacion típica. El 486 ha introducido una nueva instruccion de míquina Ahora. 17 . Esto es ocurre normalmente usando un medio de datos físico (como un disco) o una red. El apuntador p trata la variable word como dos elementos de un arreglo de caracteres.4: Como determinar lo Endianness El código en C en la figura 3. El codigo en C en la Figura 3. Así. if ( p[0] == 0x12 ) printf (”aMquina Big Endian\n”).5. Figura 3. else printf(”aMquina Little Endian\n”). p[0] evalúa el primer byte de word en la memoria que depende de lo endian en la CPU. El apuntador p trata la variable word como dos elementos de un arreglo de caracteres. Y las bibliotecas de suministran funciones de C UNICODE soporta ambos para tratar la cuestion endian de una manera portátil.5 muestra una funciín de C que invierte lo endian de una palabra doble. Ya que el codigo ASCII es de 1 byte la característica endian no le es Ahora con los conjuntos de caracteres multibyte como importante. así convertir de big a little o de little a big es la misma operación.1. La Figura 3.4 muestra cúmo se puede determinar lo endian de una CPU. Richard Steven UNIX Network Programming. 3. escribir programas de res que compilaran y se ejecutaran correctamente en cualquier sistema sin importar lo endian. La tiene un mecanismo para funcion ntohl () hace la transformacion inversa. /* se asume sizeof(short) == 2 */ unsigned char * p = (unsigned char *) &word. UNICODE .4 muestra como se puede determinar lo endian de una CPU. lo endian es Todos los encabezados internos de TCP/IP almacena enteros en big Endian importante aún para texto. Para mas informacion sobre lo endian y programacion de redes vea el excelente libro de W. 17 Para un sistema big endian. Así. REPRESENTACIONES LITTEL ENDIAN Y BIG ENDIAN 61 unsigned short word = 0x1234. p [0] evalúa el primer byte de word en la memoria que depende de lo endian en la CPU. invertir lo endian de un entero simplemente coloca al revés los bytes. ambas funciones hacen la misma cosa. Esto le permite a uno ando para representar los datos. (llamado orden de byte de la red). las especificar cuál se está usdos funciones solo retornan su entrada sin cambio alguno.5.

6. ip [2] = xp [l].5: Funcion invert_endian llamada BSWAP que invierte los bytes de cualquier registro de 32 bits.1. /* retorna los bytes invertidos */ } Figura 3. ¿Como trabaja este metodo? En cada iteracion el bucle. Por ejemplo: bswap edx . cnt++. El nímero de iteraciones requerido para hacer el dato cero es igual al nímero de bits en el valor original del dato. Contando bits Primero se dio una tecnica directa para contar el nímero de bits que estan “encendidos” en una palabra doble. while( data != 0 ) { data = data & (data — 1). CONTANDO BITS 63 unsigned invert_endian ( unsigned x ) { unsigned invert. pero no obvio. int count_bits ( unsigned int data ) { int cnt = 0.6. La figura 3.3.6. el bucle finaliza. ip [3] = xp [0]. unsigned char * ip = (unsigned char *) & invert. ip [0] = xp [3]. 3.al . Metodo uno El primer metodo es muy simple. Por ejemplo: xchg ah. intercambia los bytes de edx Esta instruccián no se puede usar en los registros de 16 bits.6: Contando bits: metodo uno .6 muestra el codigo. return invert . } Figura 3. intercambia los bytes de ax 3. const unsigned char * xp = (const unsigned char *) &x. Esta seccion mira otros metodos menos directos de hacer esto. sin embargo la instruccion XCHG se puede usar para intercambiar los bytes en los registros de 16 bits que se pueden descomponer en registros de 8 bits. Cuando todos los bits se han apagado (cuando el dato es cero). se apaga un bit del dato. } return cnt. /* invierte los bytes individuales */ ip [1] = xp [2]. como un ejercicio de usar las operaciones de bits discutidas en este capítulo.

OPERACIONES CON BITS La lónea 6 es donde se apaga un bit del dato. return byte_bit_count [byte [0]] + byte_bit_count [byte [1]] + byte_bit_count } [byte [2]] + byte_bit_count [byte [3]]. for ( i = 0. Un metodo mas real sería precalcular la cantidad de bits para todas las posibilidades de un byte y almacenar esto en un arreglo. data. (de hecho. pero en el punto del 1 del extremo derecho ellos seróan el complemento de los bits originales de data. Entonces la palabra doble se puede dividir en 4 bytes. Ahora. Por ejemplo: data = xxxxx10000 data .2. i . i++ ) { cnt = 0. el resultado seró cero el 1 del extremo derecho en data y deja todos los otros bits sin cambio. ¿Cual sera la representacion de data -1? Los bits a la izquierda del 1 del extremo derecho serón los mismos que para data.7 muestra la implementación de esta static unsigned char byte_bit_count [256]. Cuando se hace data AND data 1.7: Metodo dos . ¿Cómo se hace esto? Considere la forma general de la representación en binario del dato y el 1 del extremo derecho de esta representacion. La figura 3. int count_bits ( unsigned int data ) { const unsigned char * byte = ( unsigned char *) & data. ¡Hay alrededor de 4 mil millones de palabras dobles! Esto significa que el arreglo sera muy grande e iniciarlo consumiría mucho tiempo. 3. cnt++. /* lookup table */ void initialize_count_bits () { int cnt. Los bits considerados de estos 4 bytes se miran del arreglo y se suman para encontrar la cantidad de bits de la palabra doble original. a menos que uno vaya a utilizar realmente el arreglo de móas que 4 mil millones de veces.64 CAPÍTULO 3. } } } byte_bit_count [i] = cnt.6. Figura 3.1 = xxxxx01111 donde X es igual para ambos námeros. Sin embargo. hay dos problemas relacionados con esta aproximación. Metodo dos Para contar bits se puede usar tambien la bósqueda en una tabla de una palabra doble arbitraria. data = i . while( data != 0 ) { /* emtodo uno */ data = data & (data — 1). se tomaróa móas tiempo iniciando el arreglo que el que requeriróa solo calcular la cantidad de bits usando el móetodo uno). La aproximacion directa sería precalcular el nuómero de bits para cada palabra doble y almacenar esto en un arreglo. Por definición cada bit despues de este 1 debe ser cero. i < 256.

3. CONTANDO BITS 65 aproximacion.6. i < 5. un bucle for se podría usar facilmente para calcular int count_bits (unsigned int x ) { static unsigned int mask[] = { 0x55555555. 3. shift *= 2 ) x = (x & mask[i]) + ( (x >> shift) & mask[i ] ). ¿Que hace esto? La constante hexadecimal 0X55 es 01010101 en binario. primero mueve todos los bits de posiciones pares a impares y usa la misma mascara para sacar estos mismos bits. del primer llamado a la funcion count_bits . La funcion initialize_count_bits debe ser llamada antes. 0x0F0F0F0F. Esta suma debe ser igual al nuómero de unos en el dato. De hecho un compilador inteligente podría convertir la versióon del bucle for a la suma explócita. La funcián count_bits mira la variable data no como una palabra doble. los bits en las posiciones pares se sacan. El Segundo operando (data >> 1 & 0x55). Metodo tres Hay otro móetodo ingenioso de contar bits que estóan en un dato. Un ultimo punto. El apuntador dword actía como un apuntador a este arreglo de 4 bytes. Esta funcián inicia el arreglo global byte_bit_count. return x. el primer operando contiene los bits pares y el segundo los bits impares de data. sin embargo. Claro esta uno podría usar una instruccion como: (data >> 24) & 0x000000FF Para encontrar el byte mas significativo y hacer algo parecido con los otros bytes. Así dword [0] es uno de los bytes en data ( el byte más significativo o el menos significativo dependiendo si es little o big endian respectivamente). Este proceso de reducir o eliminar iteraciones de bucles es una tecnica de optimización conocida como loop unrolling. int i . En el primer operando de la suma data es AND con el. Por ejemplo considere calcular los unos en un byte almacenado en una variable llamada data. estas construcciones serán mas lentas que una referencia al arreglo. se suman . /* Unmero de posiciones a desplazarse a la derecha */ for ( i=0. Ahora. i++. 0x00FF00FF. Este móetodo literalmente anñade los unos y ceros del dato unido. } Figura 3. Calcular la suma como la suma explócita de 4 valores sera mós rápido. El primer paso es hacer la siguiente operacióon: data = (data & 0x55) + ((data >> 1) & 0x55). 0x33333333. comparar el óndice luego de cada iteracion e incrementar el óndice.8: Metodo tres la suma en las líneas 22 y 23. 0x0000FFFF }. sino como un arreglo de 4 bytes. int shift . Cuando estos dos operandos se suman. shift =1. Pero el bucle for incluiría el trabajo extra de iniciar el óndice del bucle.3.6.

entonces: .66 CAPÍTULO 3. OPERACIONES CON BITS los bits pares e impares de data. Por ejemplo si data es 101100112.

El siguiente paso podría ser: data = (data & 0x33) + ((data >> 2) & 0x33). no hay posibilidad de que la suma desborde este campo y dañe otro de los campos de la suma. sumas unidas para + . Continuando con el ejemplo de arriba ahora 011000102): data & 001100112 + Ahora hay 2 campos de 4 bits que se El proximo paso es sumar estas dos conformar el resultado final: data = (data & 0x0F) + ((data >> 4) & 0x0F). 0010 0001 0011 0010 0000 0010 (recuerde que data es (data >> 2) & 001100112 or suman independientemente. sin embargo. el nuúmero total de bits no se ha calculado auún. Usando el ejemplo de arriba (con data igual a 001100102): data & 000011112 00000010 + (data >> 4) & 000011112 or + 00000011 00000101 Ahora data es 5 que es el resultado correcto.3. Podría ser mas rápido deshacer el bucle. bits del byte se dividen en 4 campos de 2 bits para mostrar que se realizan 4 sumas independientes. La Figura 3. CONTANDO BITS data & 010101012 or + (data >> 1) & 010101012 67 + 00 01 01 01 00 00 01 01 10 Los 01 10 00 los bits sumados. Ya que la mayoría de estas sumas pueden ser dos.6. el bucle clarifica como el metodo generaliza a diferentes tamanños de datos. Usa un bucle for para calcular la suma. Sin embargo la misma tecnica que se usú arriba se puede usar para calcular el total en una serie de pasos similares. Claro estúa.8 muestra una implementacioún de este múetodo que cuenta los bits en una palabra doble.

Una gran parte de este capítulo tratara de las convenciones de llamado estandares de C. Que EBX se asume que señala está totalmente determinada por que instrucciones se usan. Las funciones y los procedimientos son ejemplos. que se pueden usar para interfasar subprogramas de ensamblador con programas de C. se leería un solo bate. Esta es una de las muchas razones 68 . Es importante notar que los registros no tienen ningín tipo como lo hacen las variables en C. la línea 3 lee una palabra comenzando en la direccion almacenada en EBX. se encierra entre paréntesis cuadrados ([]) por ejemplo: 1 2 3 mov ax. El codigo que llama en subprograma y el subprograma en sí mismo deben estar de acuerdo en como se pasan los datos entre ellos. Estas (y otras convenciones) a menudo pasan direcciones de datos ( apuntadores) para permitirle al subprograma acceder a datos en la memoria. Direccionamiento indirecto El direccionamiento indirecto le permite a los registros comportarse como variables apuntador. a menudo no habra error en el ensamblador. Data mov ax. [ebx] . ebx = & Data .Capítulo 4 Subprogramas Este capítulo se concentra en el uso de subprogramas para hacer los programas modulares e interfaces con programas de alto nivel (como C). [Data] mov ebx. 4. ax = *ebx Porque AX almacena una palabra. en lenguajes de alto nivel de subprograma.1. Si EBX se utiliza incorrectamente. Estas reglas de como se pasaran el dato son llamadas convenciones de llamado. sin embargo el programa no trabajará correctamente. Si AX lee reemplazando con AX. Para indicar que un registro se va a usar indirectamente como apuntador. Direccionamiento directo de memoria de una palabra .

0 y ". ---------------------------------------------------. Todos los registros de 32 bits de proposito general (EAX. El codigo siguiente muestra cómo se puede realizar esto usando una forma indirecta de la instruccion JMP.data db db db db db no olvide el NULL segment . setup routine _asm_main: enter 0.go desde la que se la invoco. file: sub1. 4.4.asm . SENCILLO SUBPROGRAMA DE EJEMPLO 69 por la cual el ensamblador es mas propenso a errores que los lenguajes de alto nivel. Si el subprograma es usado en diferentes partes del programa debe retornar a la parte del cóodi.sub1. ECX. Aca esta el primer programa del capótulo 1 reescrito para usarlo como subprograma.text g lobal _asm_main . 0 . 0 Ud. Se puede usar un salto para invocar el subprograma. Así. Sencillo subprograma de ejemplo Un subprograma es una unidad independiente de codigo que puede ser usada desde diferentes partes de un programa. pero el retorno representa un problema.2.bss inputl resd 1 input2 resd 1 segment . Subprograma programa de ejemplo %include "asm_io. EBX. el salto de retorno desde el subprograma no puede ser a una etiqueta.asm ------------------------------------------------------. En otras palabras. En general los registros de 8 y 16 bits no. EDX) y los registros de óndice (ESI y EDI) se pueden usar para el direc.inc" Ingrese un número: ". 0 segment promptl prompt2 outmsgl outmsg2 outmsg3 . Esta forma de la instruccion JMP usa el valor de un registro para determinar a dónde saltar (asó.2. la suma de ellos es ". un subprograma es como una función en C. 0 Ingrese otro número: ".cionamiento indirecto.0 pusha . el registro se comporta como un apuntador a una función en C). ha ingresado ".

70 mov call mov mov jmp retí: mov call mov mov jmp mov add mov mov call mov call mov call mov call mov call mov call call ; imprime el prompt eax, promptl print_string ebx, inputl ecx, retl short get_int eax, prompt2 print_string ebx, input2 ecx, \$ + 7 short get_int eax, [inputl] eax, [input2] ebx, eax eax, outmsgl print_string eax, [inputl] print_int eax, outmsg2 print_string eax, [input2] print_int eax, outmsg3 print_string eax, ebx print_int print_nl

CAPÍTULO 4. SUBPROGRAMAS

eax = palabra doble en inputl eax += palabra doble en input2 ebx = eax

almacena la dirección de inputl en ebx almacena la dirección de retorno en ecx lee un entero ; imprime el prompt

; imprime el primer mensaje ; imprime inputl ; imprime el segundo mensaje

; ecx = esta dirección + 7

; imprime input2 ; imprime el tercer mensaje

4.2. SENCILLO SUBPROGRAMA DE EJEMPLO
; imprime nueva línea ; imprime sum (ebx) popa mov eax, 0 ; retorno a C leave ret subprogram get_int Parameters: ebx - address of dword to store integer into ecx - dirección de la instruccion a donde retornar Notes: value of eax is destroyed

71

72 64 65 66 67

CAPÍTULO 4. SUBPROGRAMAS
get_int: call read_int mov [ebx], eax ; almacena la entrada en memoria jmp ecx ; salta al llamador _________ subl.asm ____________________________________ El subprograma get_int usa una convencion de llamado simple basada en un registro. Ella espera que el registro EBX almacene la direccion de la DWORD a almacenar el número de entrada y el registro ECX a almacenar el cúdigo de la instruccion a saltar. En las líneas 25 a 28, el operador $ se usa para calcular la direcciún de retorno. El operador $ retorna la direcciún actual para la lúnea en que aparece. La expresion $ + 7 calcula la direcciún de la instrucciún MOV de la lúnea 36. Los dos cúalculos de la direcciúon de retorno son complicados. El primer múetodo requiere que una etiqueta se defina en cada llamado a subprograma. El segundo múetodo no requiere una etiqueta, pero requiere un tratamiento cuidadoso. Si fue usado un salto NEAR en lugar de un short ¡el número a añadirle a $ podría no ser 7! Afortunadamente hay una manera mucho más simple de invocar subprogramas. Este múetodo usa la pila.

4.3.

La pila

Muchas CPU tienen soporte para una pila. Una pila es una lista LIFO (Last In Firist Out). La pila es un arca de memoria que esta organizada de esta manera. La instruccion PUSH añade datos a la pila y la instrucciún POP quita datos. El dato extraúdo siempre es el último dato insertado (esta es la razon por la cual es llamado FIFO). El registro de segmento SS especifica el segmento de datos que contiene la pila. (Normalmente este es el mismo segmento de datos). El registro ESP contiene la direccion del dato que sería quitado de la pila. Los datos solo se pueden anñadir en unidades de palabras dobles. Esto es, que no se puede insertar un solo byte en la pila. La instruccion PUSH inserta una palabra doble18 en la pila restandole 4 a ESP y entonces almacena la palabra doble en [ESP]. La instrucciún POP lee la palabra doble almacenada en [ESP] y luego añade 4 a ESP. El cúodigo siguiente demuestra cúomo trabajan estas instrucciones asumiendo que el valor inicial de ESP es 1000H.
1 2 3

push dword 1 ; 1 almacendao en OFFCh, ESP = OFFCh push dword 2 ; 2 almacenado en 0FF8h, ESP = 0FF8h push dword 3 ; 3 almacenado en 0FF4h, ESP = 0FF4h

18Actualmente también se pueden empujar palabras, pero en el modo protegido de 32 bits es mejor trabajar solo con palabras dobles en la pila.

4. El programa anterior se puede reescribir usando estas nuevas instrucciones cambiando las lóneas 25 a 34 por: mov call mov call ebx.4. La instruccion CALL hace un salto incondicional a un subprograma y empuja en la pila la direccion de la próxima instruccion. ESP = . EOI y EBP (no en este orden). ESP = 0FF8h . pasando parámetros y variables locales. LAS INSTRUCCIONES CALL Y RET 4 5 6 73 pop pop pop eax ebx ecx . La instruccion RET saca una direccion de la pila y salta a esta direccion. EDX. ESI. EAX = 3. es muy importante que uno administre la pila correctamente ya que la instruccion RET debe extraer de la pila el nuómero correcto. Cuando se usa esta instrucción. eax . 4. El 80x86 tambien suministra la instrucción PSHA que empuja el valor de los registros: EAX. ESP OFFCh = 1000h La pila se puede usar como un almacen de datos temporal muy conveniente.4. Tambien se usa para el llamado a subprogramas. EBX. EBX = 2. ECX. Las instrucciones CALL y RET El 80X86 suministra dos instrucciones que usa la pila para hacer llamados a subprogramas rápido y facil. input2 get_int y cambiando el subprograma get_int a: get_int: call mov ret Hay varias ventajas de CALL y RET ■ Es simple ■ Permite a los subprogramas hacer llamados anidados facilmente. Observe que get_int llama read_int. Al final del codigo de red_int hay un RET que saca la di. Esta llamada empuja otra direccion en la pila.reccion read_int [ebx]. ECX = 1. inputl get_int ebx. La instruccion POPA se puede usar para devolver todos estos registros a su valor anterior.

Un subprograma reentrante puede ser llamado en cualquier punto del programa con seguridad (aín dentro del subprograma mismo). 4. en lugar de ello son accedidos desde la pila misma. Si el tamaño del parámetro es menor que una palabra doble. Cuando la instruccián RET de get_int se ejecuta. si el parámetro es cambiado por el subprograma se debe pasar la dirección del dato no su valor. Convenciones de llamado Cuando un subprograma se invoca.5. <<saca el valor de EAX. saca la direccián de retorno que salta de nuevo a asm_main.74 CAPÍTULO 4. Por ejemplo considere lo siguiente: 1 2 3 4 5 get_int: call read_int mov [ebx]. Los parámetros no son sacados de la pila por el subprograma. el codigo llamado y el subprograma (el llamador) deben estar de acuerdo en como se pasan datos entre ellos. Estas convenciones le permiten a uno crear subprogramas que sean reentrantes. no la dirección de retorno!! Este codigo no retornaría correctamente. Tal como en C. Esto trabaja correctamente por la propiedad LIFO de la pila. 4. Para interfasar codigo de alto nivel con lenguaje ensamblador. se debe convertir a palabra doble antes de ser empujada en la pila. conocidas como convenciones de llamado.5. Todos los compiladores de C para PC soportan una convencion de llamado que sera descrito en el resto del capítulo por etapas. Ellos se empujan en la pila antes de la instruccián CALL. este debe usar las mismas convenciones que el lenguaje de alto nivel.1. Recuerde es muy importante sacar todos los datos que se han empujado en la pila. Una convencion universal es que el codigo sera invocado con la instruccián CALL y retornara con RET. Los lenguajes de alto nivel tienen maneras estandares de pasarse datos. SUBPROGRAMAS de retorno y que salta de nuevo al cádigo de get_int. Pasando parámetros en la pila Los parámetros a un subprograma se pueden pasar en la pila.¿Por que? . eax push eax ret . Las convenciones de llamado pueden diferir de compilador a compilador o puede variar de cámo se compila el codigo (si se ha optimizado o no).

2 muestra cúomo se ve la pila si una palabra doble se empuja en ella. Para resolver este problema. La Figura 4. la direccion de retorno tendría que haberse sacado primero (y luego metido otra vez). CONVENCIONES DE LLAMADO ESP + 4 ESP 75 Parámetro Dirección de retorno Figura 4. porque para ellos los segmentos de datos y de pilas son los 19Es valido añadir una constante a un registro cuando se usa direccionamiento indirecto. Al final del subprograma.5. Cuando el subprograma se invoca.1. . la pila se ve como en la Figura 4. ECX y EDX usan el segmento de datos. EBX. Asú. el 80386 suministra otro registro para usar: EBP. Considere un subprograma al que se le pasa un solo paraúmetro en la pila. Esto le permite a ESP cambiar cuando los datos se empujen o se saquen de la pila sin modificar EBP. Dejúandolos en la pila tenemos una copia del dato en memoria que se puede acceder en cualquier parte del subprograma. Por ejemplo. La convencioún de llamado de C ordena que un subprograma primero guarde el valor de EBP en la pila y luego lo haga igual a ESP. esto puede ser muy propenso a errores usar ESP cuando uno se refiere a parámetros.4. Este tópico se vera en el capítulo siguiente. se debe restaurar el valor de EBP (esta es la razún por la cual se guarda el valor al principio del subprograma). Si la pila se usa dentro del subprograma para almacenar datos. Normalmente. el procesador 80X86 accede a segmentos diferentes dependiendo de qué registros se usan en la expresién de direccionamiento indirecto.1: Paróametro Direccioón de retorno datos del subprograma Figura 4. ■ A menudo los parámetros tendrán que usarse en varios lugares en el subprograma.3 muestra la forma general de un subprograma que sigue estas convenciones. el nuúmero necesario a ser agregado a ESP cambiara. Ahora el paraúmetro es ESP + 8 y no ESP + 4. esto normalmente no tiene importancia para la mayoréa de los programas en modo protegido. El uúnico propoúsito de este registro es referenciar datos en la pila. Se pueden construir expresiones mas complicadas tambien. ellos no se pueden dejar en un registro durante todo el subprograma y tendría que almacenarse en memoria. Sin embargo.2: ESP + 8 ESP + 4 ESP ■ Ya que ellos se han empujado a la pila antes de la instrucciún CALL. Se puede acceder al parámetro usando direccionamiento indirecto ([ESP+4] )19. ESP (y EBP usan el segmento de la pila) mientras que EAX. Cuando se usa direccionamiento indirecto. la Figura 4.

La convencióon de C permite las instrucciones para realizar esta operacion facilmente de un llamado a otro.4 muestra como se ve la pila inmediatamente despues del prólogo. MS Windows puede usar esta convencion ya que ninguna de las funciones del API toma un nuómero variable de argumentos. Por ejemplo la convencióon de llamado de Pascal especifica que el subprograma debe quitar los parámetros de la pila (hay otra forma de la instruccion RET que hace esto facil). La convencióon de llamado de C especifica que el codigo llamador debe hacer esto. restaura el valor original de EBP Figura 4. De hecho. ¿Cual es la ventaja de este modo? Es un poco mas eficiente que la convención de llamado de C.4: Las lóneas 2 y 3 de la Figura 4. los paraómetros que se empujan en la pila se deben quitar. La figura 4. Luego que el subprograma culmina.76 1 2 3 4 5 6 CAPÍTULO 4. esp . Las convenciones de Pascal y stdcall hacen esta operacion muy difícil. Otras convenciones son diferentes. . Algunos compiladores de C soportan esta convencion tambióen. El identificador pascal se usa en la definicioón del prototipo de la funcióon para decirle al compilador que use esta convencióon. nuevo EBP = ESP .3: Forma subprograma Parámetro Dirección de general de un ESP + 8 EBP + 8 ESP + 4 EBP + 4 ESP EBP retorno EBP guardado Figura 4. SUBPROGRAMAS subprogram_label: push ebp mov ebp. Asó. la convención stdcall que usan las funciones de C del API de MS Windows trabajan de esta forma.3 componen el prologo general de un subprograma. guarda el valor original de EBP en la . Para este tipo de funciones. ¿Por que todas las funciones no usan esta convencion entonces? En general C le permite a una función tener un nómero variable de argumentos(printf y scanf son ejemplos). Las lóneas 5 y 6 conforman el epilogo. la convencióon de Pascal (como el lenguaje Pascal) no permite este tipo de funciones. subprogram code pop ebp ret . la operacióon de quitar los paraómetros de la pila variaraó de un llamado de la funcióon a otra. Ahora los parámetros se pueden acceder con [EBP + 8] en cualquier lugar del subprograma sin importar quóe haya empujado en la pila el subprograma.

4 como parámetro parámetro de la pila Figura 4. . i = 1.while( get_int(i. La línea 3 quita los parámetros de la pila manipulando directamente el apuntador de la pila. input != 0 ) { . Luego esta otro programa de ejemplo con dos subprogramas que usan la convencion de llamado de C discutida arriba. . segment .5 muestra como sería invocado un subprograma usando la convencion de llamado de C.5: Muestra del llamado a un subprograma La Figura 4. Dividir el codigo y los datos en segmentos separados permite que los datos de un subprograma se definan cerca del codigo del subprograma. La línea 54 (y otras líneas) muestran que se pueden declarar varios segmentos de datos y texto en un solo archivo fuente.5. } . POP tambien altera el valor de ECX. Ellos serán combinados en un solo segmento de texto y datos en el proceso de encadenamiento. pseudo-código . CONVENCIONES DE LLAMADO 77 . Una instruccián POP se podría usar para hacer esto pero requeriría que el resultado inítil se almacene en un registro. .print_sum(num). Sin embargo. %include "asm_io. quita el push dword 1 call fun add esp.4.data sum dd 0 sum segment . .text . sum += input. i++.asm . Actualmente. pasa 1 . El compilador usaría POP en lugar de ADD porque ADD requiere mas bytes para la instruccion. &input).bss input resd 1 sub3. para este caso en particular muchos compiladores podrían usar una instruccián POP ECX para quitar el parámetro.inc" segment . sum = 0.

guarda . [input] eax. empuja . eax edx short while_loop . 0 segment . quita i ’i’ ’i’ la e en el pseudocodigo en la pila dirección de input en &input de la pila add inc jmp .78 global _asm_main _asm_main: enter 0. setup routine edx. quita [sum] de la pila . 0 end_while [sum].text get_int: push ebp . Notas: . némero de input (en [ebp + 12]) . 1 edx dword input get_int esp. address of word to store input into (at [ebp + 8]) . subprograma get_int .0 pusha mov op: push push call add mov cmp je CAPÍTULO 4. Paramétros (en el orden que es empujan en la pila) . empuja el valor de sum en la pila . sum += input end_while: push call pop popa dword [sum] print_sum ecx leave ret . 8 eax. Los valores de eax y ebx se destruyen segment .data prompt db ") Ingrese un entero (0 para salir): ". SUBPROGRAMAS . edx es .

[ebp+8] print_int print_nl pop ebp ret -------------------------------------. [ebp + 8] [ebx]. retorna al llamador . CONVENCIONES DE LLAMADO 79 mov ebp.sub3. almacena input en memoria subprograma print_sum imprime la suma Parameter: suma a imprimir (en [ebp+8]) Nota: destrueye el valor de eax segment .5.text ebp ebp.data result db segment print_sum: push mov . [ebp + 12] call print_int mov eax. esp mov eax. eax ebp . esp "La suma es ".4. result print_string eax. prompt call print_string call mov mov pop ret read_int ebx. 0 mov call mov call call eax.asm .

En otras palabras. incluido en subprograma en sí mismo.8 muestra como se podría escribir un programa equivalente en ensamblador. La Figura 4. < II i++) sum += i.7. ebp ebp . . Considere la función de C en la Figura 4. Las variables locales son almacenadas justo despues que se guardo el valor EBP en la pila. Los datos almacenados en la pila solo usan la memoria cuando el subprograma que los define esta activo.6 muestra el nuevo esqueleto del subprograma. int * sump ) { int i . Un programa reentrante trabajara si es invocado en cualquier lugar.7: versión de C de sum 4. Usar la pila para las variables tambien ahorra memoria. } Figura 4. esp esp. *sump = sum. los programas reentrantes pueden ser invocados recursiva. SUBPROGRAMAS push mov sub . = # de bytes necesitados por las . Ellas son colocadas restando el námero de bytes requeridos de ESP en el prologo del subprograma.mente. subprogram mov PoP ret ebp ebp. Usar la pila para las variables es importante si uno desea que el programa sea reentrante. El registro EBP se usa para acceder a las variables locales. Variables locales en la pila La pila se puede usar como un lugar adecuado para las variables locales. restaura el valor original de EBP n la pila variables locales Figura 4.2. nuevo EBP = ESP .80 subprogram_label: i 2 3 4 5 6 7 8 CAPÍTULO 4. Ahí es exactamente donde C almacena las variables normales (o automatic como se dice en C). sum = 0.5. Los datos no almacenados en la pila están usando la memoria desde el comienzo hasta el final del programa (C llama este tipo de variable globalo static). libera las variables locales . guarda el valor original de EBP e . n = . La Figura 4. LOCAL_BYTES code esp.6: Forma general de un subprograma con variables locales void calc_sum( int n .

4.5. eax esp. sum = 0 . Esta parte de la pila que contiene la informacion de retorno. Este es un ejemplo de cuando uno no puede asumir que una sola instrucción es mas rápida que una secuencia de instrucciones. 1 ebx. [ebp+8] end_for [ebp-4]. los parámetros y las variables locales es llamado marco de pila (stack frame). El primer operando es el nuámero de bytes necesarios para las variables locales. Para la convencion de llamado de C. El prologo y el epálogo de un subprograma se pueden simplificar usando dos instrucciones especiales que están diseñadas específicamente para este propásito. La instruccion LEAVE no tiene operandos. CONVENCIONES DE LLAMADO cal_sum: push mov sub mov mov for_loop: cmp jnle add inc jmP end_for: mov mov mov mov PoP ret ebx. La instruccion ENTER toma dos operandos inmediatos. sum += i Figura 4. ebx (i) = 1 . 4 dword [ebp . La Figura 4. hace espacio para la sum local . Observe que el programa esqueleto (Figura 1.7) tambien usa ENTER y LEAVE. ebx ebx short for_loop 81 . [ebp-4] [ebx]. La instruccián ENTER ejecuta el cádigo del prologo y LEAVE ejecuta el epálogo. ¿Por qué? Porque ellas son mas lentas que instrucciones equivalentes mas simples. [ebp+12] eax. esp esp. .8: Versión en ensamblador de sum La Figura 4. es i <= n? .10 muestra cámo se usan estas instrucciones. *sump = sum. Cada invocacion a una funcion de C crea un nuevo marco de la pila en la pila.9 muestra como se ve la pila luego del prólogo del programa en la Figura 4. eax = sum . ebp ebp. el segundo operando es siempre 0.8. ebx = sump . A pesar del hecho que ENTER y LEAVE simplifi- can el prologo y el epilogo ellos no se usan muy a menudo. ebp ebp .4]. 0 ebx.

inc define las . O sea. Todos los programas presentados aca han sido multimodulo.9: i 2 3 4 5 6 CAPÍTULO 4. = número de . se debe usar la directiva extern. The first operand is the number bytes needed by local variables. Para que el modulo A use la etiqueta definida en el modulo B.7) also uses ENTER and LEAVE.10: Forma general de un subprograma con variables locales usando ENTER and LEAVE The prologue and epilogue of a subprogram can be simplified by using two special instructions that are designed specifically for this purpose. esas son etiquetas que se pueden usar en este mádulo pero están definidas en otro. El archivo asm_io. 0 . The ENTER instruction takes two immediate operands.6. subprogram code leave ret . variables bytes necesitados por locales Figura 4. Luego de la directiva extern viene una lista de etiquetas separadas por comas. For the C calling convention.4 sump n Direccion de retorno EBP guardado sum subprogram_label: enter LOCAL_BYTES. Recuerde que el encadenador combina los archivos objeto en un solo programa ejecutable. Note that the program skeleton (Figure 1. Programas Multinmodulo Un programa multimodulo es uno que está compuesto de mas de un archivo objeto. SUBPROGRAMAS EBP + 12 EBP + 8 EBP + 4 EBP EBP . The LEAVE instruction has no operands.10 shows how these instructions are used. 4. Figure 4. El encadenador debe emparejar las referencias hechas para cada etiqueta en un modulo (archivo objeto) con su definición en otro modulo. La directiva le dice al ensamblador que trate esas etiquetas como externas al mádulo. The ENTER instruction performs the prologue code and the LEAVE performs the epilogue. Ellos están compuestos del archivo objeto driver y el archivo objeto de ensamblador (mas los archivos objeto de las bibliotecas de C). the second operand is always 0.82 ESP + 16 ESP + 12 ESP + 8 ESP + 4 ESP Figura 4.

eax . Luego esta el codigo para el ejemplo anterior. sum += input inc edx jmp short while_loop .6.4. [input] cmp eax. no se puede acceder externamente a las etiquetas por defecto. 1 . debe declararse global en su modulo. Los dos subprogramas (get_int y print_sum) estín en archivos fuentes diferentes que la rutina _asm_main. debería haber error del encadenador. como externas.text global _asm_main extern get_int. la línea 13 del programa esqueleto listado en la Figura 1.data sum dd 0 sum segment . reescrito para usar dos mídulos. empuja la dirección input en la pila . PROGRAMAS MULTINMODULO 83 rutinas read_int.7 muestra que la etiqueta _asm_main está definida como global. guarda mov eax.asm %include "asm_io. quita i e &input de la pila push push call add edx dword input get_int esp.0 pusha mov while_loop: edx. edx es ’i’ en el pseudocodigo i en la pila . setup routine enter 0. ¿Por que? Porque el codigo de C no podría hacer referencia a la etiqueta interna _asm_main.inc" segment . etc. La directiva global hace esto. Si una etiqueta puede ser accedida desde otros moídulos diferentes al cual se definio. print_sum _asm_main: . 8 . main4.bss input resd 1 segment . 0 je end_while mov add [sum]. En ensamblador. Sin esta declaracion.

0 global get_int.inc" segment . eax .text print_sum: mov call enter 0. prompt call print_string call read_int mov ebx. 0 . empuja el valor de sum de la pila .asm sub4.data prompt db segment . [ebp + 8] mov [ebx]. print_sum get_int: enter 0.text dword [sum] print_sum ecx .84 CAPÍTULO 4. ") Ingrese un número entero (0 para salir): ". almacena la entrada en memoria leave ret segment result db segment .asm - .data "La suma es ". retorna print_string . SUBPROGRAMAS end_while: push call pop popa leave ret %include "asm_io.0 eax. quita sum de la pila main4.0 mov eax. result . [ebp + 12] call print_int mov eax.

7. Las rutinas de ensamblador comónmente se usan con C por las siguientes razones: ■ Se necesita acceso directo a características del hardware del computador que es imposible o difícil acceder desde C. [ebp+8] print_int print_nl 85 sub4. 20GAS es el ensamblador que usan todos los compiladores GNV. pocos programas estan escritos completamente en ensamblador. 4. sin embargo hay desventajas del ensamblado en línea.33 34 35 36 37 38 39 4.7. Interfazando ensamblador con C Hoy día. Cuando se usa ensamblador. (Especialmente si se activan las optimizaciones del compilador). La tecnica de llamar una rutina en ensamblador esta mucho mas generalizada en el PC. se usa a menudo para pequeñas partes de codigo. El ensamblado en línea le permite al programador colocar instrucciones de ensamblador directamente en el cóodi.asm El ejemplo anterior solo tiene etiquetas de código global sin embargo las etiquetas de datos trabajan exactamente de la misma manera. Borland y Microsoft requieren el formato NASM . Usa la sintaxis AT&T que es muy diferente de la sintaxis relativamente similares de MASM. DJGPP y el gcc de Linux requieren el formato GAS20. Ademas. es mas popular. ■ La rutina debe ser lo mas rápida posible y el programador puede optimizar a mano el codigo mejor que el compilador La ultima razon no es tan valida como una vez lo fue. Ya que es mucho mós facil escribir codigo en un lenguaje de alto nivel. No hay compilador que en el momento soporte el formato NASM. TASM y NASM. Los diferentes compiladores requieren diferentes formatos. El codigo en ensamblador se debe escribir en el formato que usa el compilador. La tecnología de los compiladores se ha mejorado con los años y a menudo generan un codigo muy eficiente. el código de alto nivel es mucho mós portatil que el ensamblador. Los compiladores son muy buenos en convertir código de alto nivel en un código de maquina eficiente. Esto se puede hacer de dos maneras: llamando rutinas de ensamblador desde C o ensamblado en línea. .go de C. Esto puede ser muy conveniente. INTERFAZANDO ENSAMBLADOR CON C mov call call leave ret eax.

2. significa que •77 e e para si se hace un cambio en sus valores. observe el guión bajo . EDI.7.11: Llamado valor de x dirección de EBP + 12 EBP + 8 EBP + 4 EBP a printf la cadena con formato Dirección de retorno EBP guardado Figura 4.text push push call add dd 0 db "x = %d\n”. SUBPROGRAMAS x format segment . dedarucíón subrutina no puede cambiarlos internamente. 0 dword [x] dword format _printf esp. <~J> ± llamada f se le asignara la etiqueta _f. Ahorrando registros se de La palabra reservada Primero.7.86 segment . En vez de ello. Sin embargo hay algunas características adicionales que necesitan ser descritas. ESI y EDI no se deben que use un registro para esta variable en vez de un modificar porque C usa esos registros para variables register. DS. El compilador gcc de Linux no antepone ningón carócter. C asume que una subrutina conserva los valores de los siguientes register puede usar registros: EBX. nombres de funciones y variables globales o static. 4. Bajo los ejecutables ELF de Linux. Etiquetas de funciones modernos hacen esto automáticamente sin requerir La mayoría de compiladores anteponen un guión bajo (_) al inicio de los ninguna sugerencia. uno simplemente usaría . empuja el valor de x . deben restablecer los valores originales sugenr e mmpu ar antes que la subrutina retorne. Esto no significa que la en una. Así. SS. si esta es una rutina en ensamblador se debe llamar _f no f. CS. Los valores en EBX.data 1 2 3 4 5 6 7 3 9 1 0 CAPITULO 4.12: Pila dentro de printf Las desventajas de las rutinas en ensamblador son: portabilidad reducida y lo poco legible. empuja la dirección de la cadena con f . Los compiladores 4. quita los parametros de la pila ormato Figura 4.1. se conocen como variables register. ES. Ellas la pila se usa para guardar los valores originales de estos registros. EBP. ESI. Normalmente lugar en memoria. La mayoría de las convenciones de llamado ya se han especificado. Por ejemplo una funcion a a 7 una var%a . 8 .

ebp . este lugar en la pila sera siempre EBP + 8 no importa ensamblador pwm pr°cesar cuantos parámetros se le pasan a la funcion. Calculando las direcciones de las variables locales Hallar la direcciín de una variable local definida en el segmento data o bss es sencillo..8 ¿Por que? El valor que MOV almacena en EAX debe ser calculado por el ensamblador (esto es.4. Ya que no se hace ninguna lectura a memoria. ni estí permitido definir el tamaño de la memoria (dword).no sera el valor de x. uno no puede usar: mov eax. Es llamado LEA (Load Efective Adress). Lo siguiente calcularía la direccion de x y la almacena en EAX: lea eax. .4. stdarg. La Figura 4. la Figura 4.8] Ahora EAX almacena la direccion de x y podría ser empujada en la pila cuando se llame la funcion foo. ¡La instrucciín LEA nunca lee la memoria! Solo calcula la direccion que sería leída por otra instrucciín y almacena esta direccion en el primer operando de registro. printf ("x=°/. si se comete un error. Sin embargo calcular la direccion de una variable local (o parámetro) en la pila no es directo. sin embargo esto no es verdad.11 muestra como se compilaría esto (mostrado en el formato equivalente de NASM). los argumentos de una funcion se empujan en la pila en el orden inverso que aparecen en el llamado a la funcion. dad.d/n"). Vea cualquier buen libro de C para los detalles 4. Sin embargo este procesarlos con portabili. Observe que en el programa esqueleto de ensamblador (Figura 1.cion esta leyendo el dato en [EBP—8]. Las reglas de la convencion de llamado de C fueron escritas específicamente para permitir este tipo de funciones. debe ser una constante). INTERFAZANDO ENSAMBLADOR CON C 87 la etiqueta f para la funciín de C f.7) la etiqueta de la rutina principal es _asm_main.cion que hace el cílculo deseado. 4. Sin embargo. Pasando parametros Bajo la convencion de llamado de C.x). parece como si esta instruc. no hay necesidad.7. La funcion printf es una de las funciones de la biblioteca de C que puede tomar cualquier nímero de argumentos. No se confunda. Considere la siguiente instruccion en C: printf ("x=%d/n". [ebp .12 muestra como se ve la pila luego del prólogo dentro de la funciín printf.3. el codigo de que se pue¿en usar para printf esperara imprimir una palabra doble en [EBP+12]. El cídigo printf puede ver un n'úmero arbitrario en la cadena con formato cuíntos parámetros se le debieron haber pasado y de par'ámetros en C. . Sin embargo el gcc de DJGPP antepone un guion bajo. El archivo de cabecera verlos en la en la pila.7. hay una instruc.7. Ya que la direccion de la cadena con formato No es necesario usar se empuja de íltimo. Si x estí en EBP — 8 en la pila. es una necesidad muy comín cuando se llaman subrutinas. Sin embargo. Considere el caso de pasar la direcciín de una variable (la llamaremos x) a una funciín (que llamaremos foo).h define marcos Claro esta.

la convencion stdcall sólo se puede usar con funciones que tomen un nómero fijo de parómetros (ej unas diferentes a printf y scanf).5.cion de llamado esta usando el compilador cuando llama su funcion. Los valores de punto flotante son almacenados en el registro ST0 del coprocesador matematico. GCC tambien soporta un atributo adicional regparam que le dice al compilador que use los registros para pasar hasta 3 argumentos enteros a su 21El compilador de C de Watcom es un ejemplo de uno que no usa la convención de llamado estandar por defecto. La función de arriba se podría declarar para usar esta convención reemplazando cdecl con stdcall. GCC también soporta la convención de llamado estándar. Sin embargo. A menudo los compiladores soportan otras convenciones de llamado tambiíen. Si son mas pequeños que 32 bits. Los valores tipo apuntador tambien se almacenan en EAX. La convencion de llamado de C especifica como se hace esto. ellos son extendidos a 32 bits cuando se almacenan en EAX. Ellos tambien suministran extensiones a la sintaxis de C para asignar explícitamente convenciones de llamado a funciones individuales. Normalmente. Los valores de retorno se pasan a traves de registros. La diferencia entre stdcall y cdecl es que stdcall requiere que la subrutina quite los parámetros de la pila (como lo hace la convención de llamado de Pascal). enum. use la siguiente sintaxis para su prototipo: void f( int ) __attr¡bute__((cdecl)).) Los valores de 64 bits se retornan en el par de registros EDX:EAX. Vea el codigo fuente de ejemplo para Watcom para los detalles .7. 4.6.7. El compilador GCC permite diferentes convenciones de llamado. Cuando se interfaza con lenguaje ensamblador es muy importante conocer que conven. estas extensiones no estan normalizadas y pueden variar de un compilador a otro. (el como se extienden depende de si ellos tipos son con o sin signo. La convencion de una funcion se puede declarar explícitamente usando la extension __attribute ____Por ejemplo para declarar una funciín void que usa la convenciín de llamado estíndar llamada f que toma un parámetro int. sin embargo no siempre es este el caso21. int. Todos los tipos enteros (char. Retornando valores Las funciones diferentes a void retornan un valor. Los compiladores que usan varias convenciones a menudo tienen opciones de la línea de ordenes que se pueden usar para cambiar la convencion por defecto. (Este registro se discute en el capítulo de punto flotante). Así.) se retornan en el registro EAX. etc.88 CAPÍTULO 4. el defecto es usar la convencioín de llamado estaíndar. Otras convenciones de llamado Las reglas anteriores describen la convenciín de llamado estandar de C que es soportada por todos los compiladores de C para 80x86. SUBPROGRAMAS 4.

Estas palabras reservadas actuóan como modificadoras de funciones y aparecen inmediatamente antes nombre de la funcióon en un prototipo. Las ventajas de la convencion stdcall es que usa menos memoria que cdecl. Ellas añaden a las palabras reservadas __cdecl __stdcall a C. Ejemplos El siguiente es un ejemplo como una rutina de ensamblador se puede interfasar con un programa de C (observe que este programa no usa el programa esqueleto de ensamblador (Figura 1. Por ejemplo.7. La principal desventaja es que no se puede usar con funciones que tengan un nuómero variable de argumentos. La principal desventaja es que la convencion es mas compleja.7) o el módulo driver.4.h> /* prototipo para la rutina en ensamblador */ .c) ______________________________ main5. Su principal desventaja es que puede ser mas lenta que alguna de las otras y usa mas memoria (ya que cada invocacion de la funcion requiere código para quitar los paraómetros de la pila). Algunos paraómetros pueden estar en los registros y otros en la pila. Se puede usar para cualquier tipo de funcióon de C y cualquier compilador de C.7.c ___________________________________ #include <stdio. 4. No se requiere limpiar la pila despues de la instrucción CALL. Este es un tipo comón de optimizacion que soportan muchos compiladores. Hay ventajas y desventajas para cada convencion de llamado. INTERFAZANDO ENSAMBLADOR CON C función en lugar de usar la pila. La ventaja de usar una convencion que use registros para pasar enteros es la velocidad. Usar otras convenciones puede limitar la portabilidad de la subrutina. Borland y Microsoft usan una sintaxis comón para declarar convenciones de llamado.7. La principal ventaja de la convencion cdecl es que es simple y muy flexible. la función f de arriba se podría definir para Borland y Microsoft asó: 89 void __cdecl f( int ).

0 .{ . printf (’’Sumar enteros hasta : ”). . pseudocodigo en C: . sum += i. int * sump ) . IMPORTANTE! sum = 0 . subroutinea _calc_sum . n .0 push ebx mov dword [ebp-4]. . . . *sump = sum.90 CAPÍTULO 4. void calc_sum( int n. int i. ecx es i en el pseudocódigo for_loop: .text global _calc_sum . 4 mov ecx. 2. &sum). printf (”Sum is%d\n”. sum). scanf(” %d”.} segment . &n). } _____________________________ main5.c ______________________________ __________________________________ sub5. for( i=1. return 0.hasta dónde sumar (en [ebp + 8]) .asm ________________________________ . i++ ) .apuntador a un int para almacenar sum (en [ebp + 12]) . 1 . sump . halla la suma de los enteros de 1 hasta n . Parametros: . SUBPROGRAMAS void calc_sum( int. local variable: . calc_sum(n. sum = 0. int * ) __attribute__ ((cdecl)). dump_stack 1. i <= n. sum at [ebp-4] _calc_sum: enter 4. int main( void ) { int n. sum. hace espacio para sum en la pila .

ebx = sump . si no i <= n. [ebp-4] mov [ebx]. [ebp+12] mov eax.asm ¿Por que la linea 22 de sub5. y el segundo y tercero determinan cuantas palabras dobles se muestran antes y despues de EBP respectivamente. uno puede ver que la direccion de la palabra doble para almacenar la suma es BffffB80 (en EBP + 12). cmp i y n . La linea 25 demuestra como trabaja el macro dump_stack. eax pop ebx leave ret .13: Muestra de la ejecución del programa sub5 cmp ecx.7. la dirección de retorno para la rutina es . [ebp+8] jnle end_for add [ebp-4].asm es importante? Porque la convención de llamado de C requiere que el valor de EBX no se modifique por la función llamada. Para este volcado. Si esto no se hace muy probable que el programa no trabajara correctamente. restaura ebx sub5. el nómero a sumar es 0000000A (en EBP + 8). Recuerde que el primer parametro es solo una etiqueta numerica. sum += i .4.13 muestra un ejemplo de la ejecucion de un programa. sale . La Figura 4. INTERFAZANDO ENSAMBLADOR CON C Dump # Sumar enteros hasta: 10 Stack 1 EBP = BFFFFB7 0 + BFFFFB8 16 0 + BFFFFB7 12 +8 C BFFFFB7 +4 8 BFFFFB7 +0 4 BFFFFB7 0 -4 BFFFFB6 C -8 BFFFFB6 8 Sum is 55 ESP = BFFFFB68 080499EC BFFFFB80 0000000A 08048501 BFFFFB88 00000000 4010648C 91 Figura 4. ecx inc ecx jmp short for_loop end for: mov ebx. eax = sum .

halla la suma de los enteros de 1 hasta n . . Tambien. Parámetros: . hace espacio en la pila para sum dword [ebp-4]. si no i <= n. sum = 0. sum += i. int i. el valor guardado de EBP es BFFFFB88 (en EBP). sub6. sale . la suma se podría dejar en el registro EAX. La línea 11 del archivo main5. la suma .hasta dánde se suma (en [ebp + 8]) . pseudocodigo en C: . La función calc_sum podría ser rescrita para devolver la suma como un valor de retorno en lugar de usar un parámetro apuntador. el prototipo de calc_sum podría necesitar ser alterado. Valor de retorno: . el codigo modificado en ensamblador.n . { . . 1 . sum at [ebp-4] _calc_sum: enter 4. } segment . return sum. sum += i [ebp-4]. local variable: . cmp i and n .c debería ser cambiada por: sum = calc_sum(n).8). subroutina _calc_sum .0 ecx. Ya que la suma es un valor entero. el valor de la variable local es 0 en (EBP.text global _calc_sum . SUBPROGRAMAS 08048501 (en EBP + 4). for( i=1. end_for add inc [ebp+8] jnle . int calc_sum( int n ) . i <= n.4). ecx ecx .0 mov mov . i++ ) . ecx es i en el pseudocodigo for_loop: cmp ecx. . y finalmente el valor guardado de EBX es 4010648C (en EBP.92 CAPÍTULO 4. sum = 0 .asm . Abajo esta.

7.asm ------------------------------------------------------- 4.obj.14: Llamando scanf desde ensamblador jmp short for_loop end_for: mov eax. vea el codigo en asm_io. La Figura 4. Por ejemplo.14 muestra el codigo que hace esto. 4. De hecho EAX definitivamente será cambiado. eax = sum leave ret ---------------------------------------------------. Un punto muy importante que recordar es que scanf sigue la conven. Llamando funciones de C desde ensamblador Una gran ventaja de interfasar C y ensamblador es que le permite al código de ensamblador acceder a la gran biblioteca de C y usar funciones escritas por el usuario.4.8.text lea push push call add eax.sub6.8. [ebp-4] .cion de llamado de C normalizada. Esto significa que preserva los valores de los registros EBX. Subprogramas entrantes y recursivos Un programa reentrante debe satisfacer las siguientes propiedades: .8. ya que el contiene el valor de retorno del llamado a scanf. [ebp-16] eax dword format _scanf esp.asm que fue usado para crear asm_io. sin embargo los registros EAX. 8 o 93 Figura 4. SUBPROGRAMAS ENTRANTES Y RECURSIVOS segment . ESI y EDI. Para otros ejemplos del uso de interfaces con C.data format segment . si usted desea llamar la funcion scanf para leer un entero del teclado. ECX y EDX se pueden modificar.

Linux. 2 . el subprograma foo podría llamar a bar y bar podría llamar a foo. La recursioín puede ser directa o indirecta. la instrucción anterio cambia 2 por 5 Este codigo trabajaría en modo real. solo una copia del codigo esta en memoria.1. Las bibliotecas compartidas y DLL (Dinamic Link Libraries) usan esta idea tambien. ■ Un programa reentrante puede compartirse en míltiples procesos. el programa se abortaría en estos sistemas. En muchos sistemas operativos multitarea. digamos foo se invoca así mismo dentro del cuerpo de foo. Si la rutina recursiva no tiene una condicion de terminacion o la condiciín nunca se vuelve verdadera. Esto es una mala forma de programar por muchas razones. Por ejemplo: mov word [cs:$+7]. Todas las variables son almacenadas en la pila. si hay varias instancias de un programa ejecutíndose. 5 add ax. Por ejemplo. difícil de mantener y no permite compartir el cídigo (vea despues). Hay varias ventajas de escribir codigo reentrante: ■ Un subprograma reentrante se puede llamar recursivamente.8. pero otro subprograma lo llama. etc. pero en ensamblador no es difícil para un programa intentar modificar su propio codigo. SUBPROGRAMAS ■ No debe modificar ninguna instrucciín. 4.94 CAPÍTULO 4. La recursion directa ocurre cuando un subprograma. En un lenguaje de alto nivel esto podría ser difícil. Cuando esta condicion es verdadera. La recursion indirecta ocurre cuando un subprograma no se llama así mismo directamente. la recursion nunca termina (muy parecido a un bucle infinito). pero en sistemas operativos con modo protegido el segmento de codigo esta marcado de solo lectura. ■ No debe modificar datos globales (tal como los datos que estan en el segmento data y bss). Subprogramas recursivos Estos tipos de subprogramas se invocan así mismos. ■ Programas reentrantes trabajan mucho mejor en programas multihilo5 Windows 9x/NT y la mayoría de los sistemas operativos tipo UNIX (Solaris. no se necesita hacer mas llamadas. Los programas recursivos deben tener una condición de terminación. . Es confuso. Cuando se ejecute la primera línea.) soportan programas multihilo. copia 5 en la palabra 7 bytes adelante .

1 term_cond eax eax _fact ecx dword [ebp+8] short end_fact eax. termina .0 eax. edx:eax = eax * [ebp+8] Figura 4.text global _fact: enter mov cmp jbe dec push call PoP mul jmP term_cond: mov end_fact: leave ret 0. finds n! segment .8. respuesta en eax . 1 .4. eax = fact(n-l) .15: Función factorial recursiva n=3 frame ____ nil] ______ Return address Saved EBP n(2) Saved EBP n ( i ) n=2 frame Return address Return address Saved EBP n=1 frame Figura 4. eax = n _fact 95 . [ebp+8] eax. SUBPROGRAMAS ENTRANTES Y RECURSIVOS . si n <= 1.16: Marco de la pila para la función factorial .

4. SUBPROGRAMAS La Figura 4. /* find 3! */ La Figura 4. Esto es el programa en sí mismo es multitarea. 5un . sin embargo. Las Figuras 4.96 CAPÍTULO 4.18 muestran otro ejemplo recursivo mas complicado en C y en ensamblador respectivamente. si ellas estan declaradas como static. Puede ser llamado desde C con: x = fact(3). Estas variables se almacenan tambien en lugares fijos de memoria (en data bss). pero solo se pueden acceder directamente en las funciones en donde ellas se definieron. C usa la palabra reservada static para dos propositos diferentes). Por defecto ellas pueden ser accedidas por cualquier funciín en el programa. global Estas variables estan definidas fuera de cualquier funcion y estan almacenadas en lugares fijos de memoria (en los segmentos data o bss) y existen desde el inicio hasta el final del programa.8.2. solo las funciones en el mismo modulo pueden acceder a ellas (en terminos de ensamblador la etiqueta es interna. Así cada instancia recursiva de f tiene su propia variable independiente i.16 muestra címo se ve la pila en el punto mís profundo del llamado de la funcion anterior. no externa). ¿Cual es la salida para f(3)? Observe que la instruccion ENTER crea un nuevo i en la pila para cada llamado recursivo.15 muestra una funciín que calcula el factorial recursiva. programa multihilo tiene varios hilos de ejecución.mente.17 y 4. Definiendo i como una palabra doble en el segmento data no trabajaría igual. Revisión de tipos de variables según su alcance en C C suministra varios tipos de almacenamiento para las variables. static Estas son variables locales de una funciín que son declaradas static (desafortunadamente.

0 . i++ push dword [i] call _f pop eax inc dword [i] jmp short lp quit: leave ret Figura 4. SUBPROGRAMAS ENTRANTES Y RECURSIVOS %define i ebp-4 %define x ebp+8 . macros utiles segment . [x] jnl quit .4. [i] cmp eax. 10. es i < x? 97 . i = 0 lp: mov eax.0 mov dword [i]. 10 = ’\n’ segment . llama printf . 0 .8.18: Otro ejemplo (versión de ensamblador) .data format db "%d". llama f . toma espacio en la pila para i push eax push call add format _printf esp. 8 .text global _f extern _printf _f: enter 4.

Estas variables son colocadas en la pila cuando la funciín en la cual estan definidas se invocada y quitadas cuando la funcion retorna. Tambien. y = 20. Si x pudiera ser alterada por otro hilo. solo tipos enteros pueden ser valores tipo register. es posible que otro hilo cambie x entre las líneas 1 y 3 así que z podría no ser 10. Un ejemplo comín de una variable volatile podría ser una que se alterara por dos hilos de un programa multihilo. register Esta palabra reservada le pregunta al compilador usar un registro para el dato de esta variable. . However. If x could be altered by another thread. ellas no tienen un lugar fijo en memoria. the compiler might assume that x is unchanged and set z to 10. Considere el siguiente codigo: x = 10. El compilador no tiene que hacerlo.98 CAPÍTULO 4. if the x was not declared volatile. No se pueden hacer este tipo de optimizaciones con variables volatile. Así. Si la direcciín de la variable se usa en cualquier parte del programa la variable no sera de este tipo (ya que los registros no tienen direccion). Esto es solo una solicitud. Tipos estructurados no lo pueden ser. Sin embargo. Otro uso de volatile es evitar que el compilador use un registro para una variable. volatile Esta palabra clave le dice al compilador que el valor en la variable puede cambiar en cualquier momento. SUBPROGRAMAS automatic Este es el tipo por defecto para una variable definida dentro de una funcion. z = x. ¡ellos no caben en un registro! Los compiladores de C a menudo convertirán variables normales automatic en variables register sin ningín aviso al programador. Esto significa que el compilador no puede hacer ninguna suposicion sobre cuando se modifica la variable. si x no fue declarada volatile. it is possible that the other thread changes x between lines 1 and 3 so that z would not be 10. el compilador podría asumir que x no cambia y fija z a 10. A menudo un compilador podría almacenar el valor de una variable en un registro temporalmente y usar el registro en vez de la variable en una parte del codigo.

5. define un arreglo de 10 plabras dobles con valores . pero esto complica los caálculos. segment . ■ El índice del elemento. dw. lo mismo de antes usando TIMES a3 times 10 dw 0 . define un arreglo de 10 palabras inicadas todas con 0 a2 dw 0 . 0 .1.10 al dd 1 . 0 . define un arreglo de bytes con ceros y luego 100 con unos a4 times 200 db 0 times 100 db 1 segment . 6 .. 9 .Capítulo 5 Arreglos 5. La Figura 5. 0 . Es conveniente considerar el índice del primer elemento como 0 (tal como C).1. 3 . 8 . iniciales de 1. 4 .1. Definir arreglos Definir arreglos en los segmentos data y bss Para definir un arreglo iniciado en el segmento data. Introducción Un arreglo es un bloque contiguo de una lista de datos en la memoria. etc. 0 .bss 99 . 0 .. 0 . NASM tambien suministra la directiva llamada TIMES que se puede usar para repetir una instruccion muchas veces sin tener que duplicar las instrucciones a mano. 0 . La direccion de cualquier elemento se puede calcular conociendo tres factores: ■ La direccián del primer elemento del arreglo. use las directivas normaleas: db. ■ El námero de bytes de cada elemento.data .1 muestra varios ejemplos de estos. 0 .2. 0 . 10 .. 5 . Es posible usar otros valores para el primer índic. 2 . 7 . los arreglos permiten un acceso eficiente de los datos por su posicion (o índice) en el arreglo. Por estas propiedade. Cada elemento de la lista debe ser del mismo tipo y usar exactamente el mismo nuámero de bytes de memria para almacenarlo.

use las directivas resb.2 muestra dos maneras posibles. uno calcula el el total de bytes necesarios para todas las variables locales.1 muestra tambien ejemplos de este tipo de definiciones Definir arreglos como variables locales en la pila No hay una manera directa de definir una arreglo como variable local en la pila. Sin embargo. La Figura 5.100 CAPÍTULO 5. define un arreglo de 10 palabras dobles sin valor inicial a5 .1: Definir arreglos Para definir un arreglo iniciado en el segmento bss. resw. y restar esto de ESP (o directamente usando la instrucción ENTER). incluidos los arreglos. el número restado de ESP debería ser un múltiplo de cuatro (112 en este caso) para que ESP este en el boundary de pna palabra doble. Recuerde que estas directivas tienen un operando que espera cuantas unidades de memoria reservar. si una función necesita una variable caracter. define un arreglo de 100 palabras dobles sin valor inicial a6 Figura 5. ARREGLOS resd 10 resw 100 . Por ejemplo. dos enteros y 50 elementos de un arreglo de palabras. Como antes. uno necesitaría 1 + 2 + 50 = 109 bytes. . Uno podría formar las variables dentro de los 10g bytes de varias maneras. etc. La parte sin uso del primer orden para dejar las palabras dobles en los límites de palabras dobles para aumentar la velocidad del acceso a la memoria. La Figura 5.

12 char no usado dword 1 dword 2 word array EBP .1 EBP . se referencia el elemento 1 del arreglo.apuntador para array1[3] determinar bytes mover en una mov ax. no el segundo.5.108 EBP . 4. array2[3] = ax elemento en[array2 elemento.1.2. + . mov [array1 mira + 3].2: Disposición de la pila 101 word array dword 1 dword 2 char no usado 5. el compilador de .el tipo al =cuíntos al En C. 7 leerí un byte del primer elemento y uno del segundo. =suma ?? todos los elementos de arrayl La Figura muestra de codigo ax que del ejemplo anterior.3 [array2 + un fragmento 1] . ax . ¿Por que no AL? Primero.1. 3. 1 arreglo de bytes arreglo de palabras A continuación algunos ejemplos usando estos arreglos. 2. Considere las dos definiciones siguientes de arreglos: arrayl array2 db 5. mov ax. INTRODUCCION EBP . [array1] = array1[0] son unidades de 2 bytes.5. a AX se le suma DX. no uno. para moverse al siguiente al elemento del arreglo uno se debe mover al. al = array1[1] 2mov bytes adelante. 2. [array1 + La línea 1] .112 Figura 5. En la línea 5. los dos operandos de la instruccion ADD deben . Sin embargo el mov ax. [array2] ax tenga = array2[0] expresion aritmíetica para que el programador no que hacerla.109 EBP .8 EBP . Para acceder a un elemento del arreglo. En la línea 7. Acdeder a elementos de los arreglos No hay operador [ ] en ensamblador como en C.100 EBP . ¿Por que? Las palabras 1 2 3 4 5 6 7 mov al.104 EBP . 3. 4. se debe calcular la direcciín. ax = array2[1] ensamblador es [array2 el que calcula el2] tamaño de los elementos del arreglo cuando se mueve de (NO array2[2]!) mov + 6]. 1 dw 5.

4 y 5.5 muestran dos alternativas para calcular la suma. se permite una suma hasta 65. bx++ Figura 5. 22Fijando AH a cero se asume implícitamente que AL es un número sin signo. ax ebx IP al = *ebx dx += ax (not al!) bx++ ebx = dirección de arrayl dx almacenaró sum ? lp: mov add inc loop Figura 5. inc dh .3: Sumar elementos de un arreglo (Versión 1) mov mov mov lp: ebx. Las Figuras 5. Si es con signo. arrayl dx. 0 ah. arrayl dx. ebx = dirección de arrayl . 22 en la línea 3. 5 rO r*0 .102 CAPÍTULO 5. 0 ecx. Usando DX. This is why AH is set to zero23 in line 3. Sin embargo es importante ver que se esta sumando AH tambien. if no hay carry vaya a next . la acción apropiada sería insertar una instrucción CBW entre las líneas 6 y 7 23 Setting AH to zero is implicitly assuming that AL is an unsigned number. Esta es la razán por la cual se fija AH a cero. Las líneas en italica reemplazan las líneas 6 y 7 de la Figura 5. ARREGLOS mov mov mov mov ebx. 5 al. Segundo. [ebx] dx.535. the appropriate action would be to insert a CBW instruction between lines 6 and 7 d . dl += *ebx .3. debería ser fácil sumar bytes y llegar a un resultado que no quepa en un byte.4: Sumar elementos de un arreglo (Version 2) ser del mismo tamaño. 0 ecx. dx almacenaró sum add jnc inc next: inc loop next dh ebx lp . also. If it is signed.

factor es 1. Usa el programa arraylc. ________________________________ arrayl. INTRODUCCION mov mov mov lp: ebx. ESI. ebx = direcciín de arrayl .) constant es una constante de 32 bits.data FirstMsg Prompt db "Primeros 10 elementos del arreglo". 0 ecx. EDI. La constante puede ser una etiqueta (o expresion de etiqueta). ECX.1. 0 db "Ingrese el índice del elemento a mostrar: ". EBX.4. EBP.asm ___________________________________ %define ARRAY_SIZE 100 %define NEW_LINE 10 segment . dh += bandera de carry + 0 . EDX.1. 0 ebx lp . (Si 1. 5 . EBX. EDX. La forma mas general de una referencia indirecta memoria es [ reg base + factor*reg índice + constante] donde: reg base es uno de los registros EAX. di += *ebx . ESI o EDI.1. bx++ Figura 5. Ejemplo Se presenta un ejemplo que usa un arreglo y se pasa a la funcion. ESP. EBP. 0 .3. 4 o 8. ECX. Direccionamiento indirecto más avanzado No es sorprendente que se use direccionamiento indirecto con arreglos. (observe que ESP no esta en la lista. arrayl dx. 2.c (listado abajo) como driver y no el programa driver. el factor se omite) index reg es uno de estos registros EAX. dx almacenarí sum 103 add adc inc loop di. [ebx] dh.ci. 5.5.5: Sumar elementos de un arreglo (Versión 3) 5.

eax = direccion de la variable local .0 ebx esi _puts. . ARRAY_SIZE ebx. 97. 98. 4 init_loop . 0 db "Elementos 20 hasta 29 del arreglo". variable local en EBP . imprime los 10 primeros elementos del arreglo .104 CAPÍTULO 5. 99. NEW_LINE. _scanf. 8 .text extern _dump_line global push push _asm_main _asm_main: enter 4. mov mov init_loop: mov add loop [ebx].4 .. ARREGLOS db "Elemento %d es %d". [ebp-4] push eax push dword InputFormat .. imprime FirstMsg ecx. Le pregunta al usuario el índice del elemento Prompt_loop: push dword Prompt call _printf pop ecx lea eax. array push dword FirstMsg call _puts pop ecx push push call add dword 10 dword array _print_array esp.bss resd ARRAY SIZE segment . 0 array SecondMsg ThirdMsg InputFormat segment . . ecx ebx. Se inicia el arreglo con 100. _printf. 0 db "%d".

[ebp-4] dword [array + 4*esi] esi dword SecondMsg . . 8 esi ebx eax. _puts ecx dword 10 dword array + 20*4 _print_array esp. si la entrada no es valida call jmP InputOK: mov push push push call add push call pop push push call add pop pop mov leave ret esi. dirección de array[20] . . routine _print_array Rutina llamable desde C que imprime elementos de arreglos de palabras dobles como enteros con signo Prototipo de C: void print_array( const int * a. 0 . int n). . . Prompt_loop .data . .1. 1 InputOK _dump_line . eax = valor de retorno de scanf . .5.Apuntador al arreglo a imprimir (en ebp+8 en la pila) n . 12 dword ThirdMsg . Parámetros: a . INTRODUCCION call add cmp je 105 _scanf esp. _printf esp. retorna a C imprime el valor del elemento imprime los elementos 20 a 29 .número de enteros a imprimir (en ebp+12 en la pila) segment . dump rest of line and start over . . 8 eax.

} /* * o funcin dump-line . ecx = n . empuja array[esi] . 0 segment . return ret_status . <print podría cambiar ecx! .text global _print_array array: enter 0.0 push esi push ebx xor mov mov oop: push push push push call add inc pop loop pop pop leave ret arraylc.106 CAPÍTULO 5.c esi. [ebp+12] ebx.h> int asm_main( void ). ARREGLOS OutputFormat db "7„-5d /5d". void dump_line( void ). ebx = dirección del arreglo . NEW_LINE. int main() { int ret_status . esi = 0 . ret_status = asm_main(). 12 esi ecx print_loop ebx esi arrayl. esi ecx.asm . [ebp+8] ecx dword [ebx + 4*esi] esi dword OutputFormat _printf esp. quita los parímetros (<deja #include <stdio.

1. [ebp 48] add j 4 mov eax.1. [ebp + 4*eax . 5. [ebp ebp .44 es el sitio de i 2 sal eax. Arreglos multidimiensionales Un arreglo multidimensional no es realmente muy diferente que los arreglos unidimensionales ya discutidos de hecho. Por convencion. } ____________________________ arraylc.5. Sin embargo. Un uso común es para caiculos rápidos. Usando LEA para hacer esto.52]. Un arreglo multidimensional se presenta a menudo como una malla de elementos. [4*eax + eax] Esto efectivamente almacena el valor de 5 x EAX en EBX. 1 multiplica i por 2 3 add eax.40] ebp . el primer índice es identificado con la fila del elemento y el segundo úndice la columna. Considere lo siguiente: lea ebx. Considere un arreglo con tres filas y dos columnas definidas como: 1 mov eax.6: Ensamblador para x = a[i ][j] int a [3] [2]. Así por ejemplo. eax Almacena el resultado en ebp 4 i i Figura 5. uno debe tener en cuenta que la expresion dentro de los paréntesis cuadrados debe ser una direcciún indirecta correcta.c __________________ Revision de la instrucción LEA La instrucción LEA se puede usar para otros propósitos que solo calcular direcciones. esta instruccion no se puede usar para multiplicar por 6 rúpidamente. es mas facil y rápido que usar MUL. esrán representados en memoria tal como un arreglo unidimensional.40 es la dirección a[0][0" 5 mov [ebp . El compilador de C reservaría espacio para un arreglo de 6 (= 2 x 3) enteros y . while( (ch = getchar()) != EOF && ch != ’\n’) /* cuerpo Ivacio*/ .5. Cada elemento esta identificado por un par de índices. INTRODUCCION 107 * dumps all chars left in current line from input buffer */ void dump_line() { int ch. Arreglos bidimensionales No debe sorprender que el arreglo multidimiensional mús elemental es el arreglo unidimensional ya discutido.

La formula en este caso es 2i + j. el primer elemento de la fila i esta en la posicion 2i . N x i + j. Esto es conocido como la representación rowwise del arreglo y es como un compilador de C/C++ podría representar el arreglo. El óltimo elemento de una fila es seguido por el primer elemento de la próxima fila. así.6 muestra como el ensamblador trabaja esto. . Este analisis tambien muestra címo la formula se generaliza para un arreglo con N columnas. Como un ejemplo. La Figura 5. Cada fila es de dos elementos. el compilador esencialmente convierte el cídigo a: x = *(&a[0][0] + 2* i + j). Entonces la posiciín de la columna j se encuentra sumando j a 2i. ¿Como el compilador determina donde aparece a[i][j] en la representación rowwise? Una formula elemental calcularó el índice de i y j. el programador podría escribir de esta manera con los mismos resultados. así. Cada fila del arreglo bidimensional se almacena contiguamente en memoria.108 CAPÍTULO 5. El elemento a [0 ][l] se almacena en la siguiente posición (índice 1) y así sucesivamente. Observe que la fórmula no depende del nímero de filas. No es difícil ver címo se deriva esta formula. ARREGLOS ordena los elementos como sigue: 0 Indice Elemento a[0 ][0 ] 1 2 3 a[1 ][1 ] 4 a[2 ][0 ] 5 a[2 ][1 ] a[0 ][1 ] a[1 ][0 ] La tabla intenta mostrar como el elemento referenciado como a[0 ][0 ] es almacenado en el comienzo de los 6 elementos de un arreglo unidimensional. y de hecho. veamos como compila gcc el siguiente codigo (usando el arreglo a definido antes): x = a[i ][ j ].

Para un arreglo n dimensional de dimensiones D1 a Dn. Para dimensiones mayores. Esto es importante cuando se interfaza el codigo con varios lenguajes.+ Dn X in.+ Di X D2 X ■ ■ ■ X Dn-2 x in-1 + Di X D2 X ■ ■ ■ X Dn .mensionales cada uno de tamaño [3][2] consecutivamente en memoria. La tabla de abajo muestra como comienza el: Index Element Index Element 0 1 2 b[0 ][0 ][0 ] 6 b[0 ][0 ][1 ] 7 b[1 ][0 ][1 ] b[0 ][1 ][0 ] 8 3 b[0 ][1 ][1 ] 9 b[1 ][1 ][1 ] 4 b[0 ][2 ][0 ] 10 5 b[0 ][2 ][1 ] 11 b[1 ][0 ][0 ] b[1 ][1 ][0 ] b[1 ][2 ][0 ] b[1 ][2 ][1 ] La fármula para calcular la posicion de b[i][j][k] es 6 i + 2j + k. la posicion del elemento denotada por los índices i1 a in está dada por la formula: D2 x D3 ■ ■ ■ x Dn x ii + D3 x D4 ■ ■ ■ x Dn X Í2 + ----------. Considere el arreglo tridimensional: int b [4][3][2]. se aplica la misma idea bósica.1 + in o puede ser escrito más corto como: E(n j=i \fc=j+i D Aquí es donde puede decirle al autor que fue un mejor físico (o ¿fue la referencia! a FORTRAN un regalo?) La primera dimension D1 no aparece en la formula: Por una representacion columnwise la formula general seria: il + Di X ¿ 2 + -------. El elemento [i][j] se almacenan en la posición i + 3j. INTRODUCCION 109 No hay nada mágico sobre la escogencia de la representación rowwise del arreglo. Observe que nuevamente la primera dimensián L no aparece en la formula. Dimensiones mayores que dos Para las dimensiones mayores de 2. Otros lenguajes (FORTRAN.1 x in . cada columna se almacena contigua. para un arreglo a[i][j][k] sera: M x N x i + N x j + k. El 6 esta determinado por el tamaño de los arreglos [3][2]. Una representacián columnwise podría trabajar bien: Index Element 0 1 2 a[0 ][0 ] a[1 ][0 ] a[2 ][0 ] 3 a[0 ][1 ] 4 a[1 ][1 ] 5 a[2 ][1 ] En la representación columnwise. por ejemplo) usan la representacion columnwise. En general.1. se generaliza el mismo proceso. Este arreglo debería ser almacenado como si fueran cuatro arreglos bidi.5.

la que no aparece en la fármula. Para arreglos de dimensiones mayores. es la áltima dimensión Dn. lo siguiente compila: void f( int a [ ][2] ).110 En la notación matemática griega de über: CAPÍTULO 5. . Esto es aparente cuando consideramos el prototipo de la funcion que toma un arreglo multidimensional como parámetro. Lo siguiente no compilaráa: void f( int a [ ][ ] ). no no se necesita el tamaño del arreglo para calcular donde esta localizado un elemento en memoria. Cualquier arreglo bidimensional con dos columnas puede ser pasado a esta funcion. La primera dimension no se necesita. Esto no es cierto para arreglos multidimensionales. Esto define un arreglo unidimensional de apuntadores enteros (que incidentalmente puede ser usado para crear un arreglo de arreglos que se comporta muy parecido a un arreglo bidimensional). Pasar arreglos multidimensionales como parámetros en C La representación rowwise de arreglos multidimensionales tiene un efecto directo en la programacion en C. Instrucciones de arreglos/cadenas La familia de procesadores 80X86 suministran varias instrucciones que estan diseñadas para trabajar con arreglos. un arreglo de 4 dimensiones se podría pasar como: void f( int a [ ][4][3][2] ).2. Estas instrucciones son llamadas instrucciones de cadena. Para arreglos unidimensionales. 5. Por ejemplo. /* No hay óinformacin de la odimensin */ Sin embargo. Acceder a los elementos de estos arreglos el compilador debe conocer todo menos la primera dimensiáon. pero será ignorado por el compilador. 24 No se confunda con una funcion con este prototipo: void f( int * a [ ] ). ARREGLOS n j=i En este caso. Ellas usan los registros de índice (ESI y EDI) para 24Se puede especificar un tamaño allí. se debe especificar todo menos la primera dimensián.

(tal como es DS). 5. no DS. STD Establece la bandera de direccioín. en la programaciín en modo real es muy importante para el programador iniciar ES con el valor de selector del segmento correcto. Esto a menudo hace que el codigo trabaje la mayor parte del tiempo (cuando sucede que la bandera de direcciín esta en el estado deseado) pero no trabaja todo el tiempo.5. AX o AEX). INSTRUCCIONES DE ARREGLOS/CADENAS LODSB AL = [DS:ESI] ESI = ESI ± 1 LODSW AX = [DS:ESI] ESI = ESI ± 2 111 STOSB [ES:EDI] = AL EDI = EDI ± 1 STOSW [ES:EDI] = AX EDI = EDI ± 2 LODSD EAX = [DS:ESI] STOSD [ES:EDI] = EAX ESI = ESI ± 4 EDI = EDI ± 4 Figura 5. palabra o palabra doble de una vez. Ahora observe que el registro que almacena el dato es fijo (AL. 9.data arrayl dd 1. Hay dos instrucciones que modifican la bandera direccion: CLD Borra la bandera de direcciín. observe que las instrucciones de almacenamiento usan ES para determinar el segmento donde escribir. 3.1.7 muestra estas instrucciones con una corta descripcion en pseu. Primero. . Un error muy común en la programaciín de 80X86 es olvidar colocar la bandera de direccion en el estado correcto de manera explícita. 5. 7. 6 . En lugar de ello. ya que hay solo un segmento de datos y ES debería ser iniciado automaticamente para referenciarlo.2. ESI se usa para leer y EDI para escribir. Es fíacil recordar esto si uno tiene en cuenta que SI significa Source Index y DI significa Destination Index. La Figura 5.8 muestra un ejemplo del uso de estas instrucciones segment . el valor DS debe copiarse a un registro de proposito general (como AX) y entonces copiado de este registro a ES usando dos instrucciones MOV.7: Instrucciones de cadena de lectura y escritura realizar una operaciín y entonces automaticamente incrementan o decre. En este estado los registros de índice se decrementan. 10 segment . Hay varios puntos para tener en cuenta. Sin embargo.2.mentan uno o los dos registros de índice. Finalmente. 2. 25 La Figura 5. 8 . En la programacion en modo protegido esto no es normalmente un problema. Ellas pueden leer o escribir un byte. Leer y escribir en la memoria Las instrucciones mías elementales leen o escriben en memoria.bss array2 resd 1 0 25Otra complicación es que uno no puede copiar el valor de DS en el registro de ES directamente usando la instrucción MOV.docodigo que hace ella. La bandera dirección (DF) en el registro FLAGS determina dínde los registros de índice son incrementados o decrementados. 4. los registros de índice se incrementan. En este estado.

Las láneas 13 y 14 de la Figura 5. array2 mov ecx. 5. es un byte especial que se coloca antes de una instruccion de cadena que modifica el comportamiento de la instruccián. El registro ECX se usa para contar las iteraciones (tal como la instruccián LOOP).8 (láneas 12 a 15) se podría reemplazar con una sola lánea: rep movsd 26 un prefijo de instrucción no es una instrucción.9 describe las operaciones que estas instrucciones realizan.112 CAPÍTULO 5. De hecho.8) es muy comán. El prefijo de instrucción REP La familia 80X86 suministra un prefijo de instruccion especial 26 llamado REP que se puede usar con las instrucciones anteriores Este prefijo le dice a la CPU que repita la proxima instruccián de cadena un námero especificado de veces. 1 0 lp: lodsd stosd loop lp Figura 5. La Figura 5.2. ARREGLOS segment . La ánica diferencia sería que el registro EAX no sería usado en el bucle. Usando el prefijo REP.8 debería ser reemplazada con una sola instruccián MOVSD con el mismo efecto.8: Ejemplo de carga y almacenamiento que copia un arreglo en otro.text cld . La combinacion de una instruccián LODSx y STOSx (en las líneas 13 y 14 de la Figura 5.2. arrayl mov edi. Se usan otros prefijos para sobreescribir los segmentos por defecto de los accesos a memoria . esta combinacion se puede realizar con una sola instruccián para cadenas MOVSx. <No olvide esto! mov esi. el bucle en la Figura 5.

Así si uno desea encontrar la direcciún de donde esta el 12 es necesario sustraer 4 de EDI (como lo hace la línea 16). . Ellas son útiles para comparar o buscar en arreglos. 1 0 xor eax. Las instrucciones CMPSx comparan el lugar de memoria correspondiente y SCASx comparan lugares de memoria en busca de un valor específico. INSTRUCCIONES DE ARREGLOS/CADENAS MOVS B MOVS W MOVS D byte ESI = ESI ± 1 EDI = EDI ± 1 co E i _______________________ i 113 byte [DS:ESI] word [ES:EDI] = word [DS:ESI] ESI = ESI ± 2 EDI = EDI ± 2 dword [ES:EDI] ESI = ESI ± 4 EDI = EDI ± 4 = dword [DS:ESI] Figura 5. La Figura 5.text cld mov edi.5. array mov ecx.12 muestra una porcion de codigo que busca el número 12 en un arreglo de palabras dobles. eax rep stosd <No olvide esto! Figura 5.bss array resd 1 0 segment . 5. Ellas fijan el registro FLAGS tal como la instrucción CMP.11 muestra varias instrucciones de cadena nuevas que se pueden usar para comparar memoria con otra memoria o un registro.10: Ejemplo de arreglo cero La Figura 5. Comparación de las instrucciones de cadena La Figura 5.2.1 I D E II segment .3.9: Instrucciones de cadena de movimiento de memoria i 2 3 4 5 6 7 3 9 1 ---------------------------------------------. la instruccion SCASD en la línea 10 siempre le suma 4 a EDI aún si el valor buscado se encuentra.10 muestra otro ejemplo que llena de ceros el contenido de un arreglo.2.

el registro o registros ándice se incrementan y ECX se decrementa.2.2.14 muestra una porcion codigo de ejemplo que determina si dos bloques de memoria son iguales. Muchas de las funciones duplican las funciones de las bibliotecas . sin embargo. 5. El JE en la línea 7 del ejemplo examina el resultado de la instruccion anterior.114 CAPÍTULO 5. la bandera Z estara fijada y el cádigo salta a la etiqueta equal. REPE y REPZ son sinánimos para el mismo prefijo (como RPNE y REPNZ) si la comparacián repetida de la instrucciáon de cadena se detiene por el resultado de la comparaciáon. La Figura 5. Si la comparacion repetida para porque encontráo dos bytes diferentes.4.13 muestra los dos nuevos prefijos y describe su operacion. Ejemplo Esta seccion contiene un archivo fuente en ensamblador con varias funciones que implementan operaciones con arreglos usando instrucciones de cadena. sí ECX es cero despues de Así es posible usar la bandera Z para determinar si las comparaciones rePetír la compamcwn? repetidas pararon por una comparacion o porque ECX es cero. la bandera Z continuaráa borrada y no se hace el salto. Prefijos de instrucción REPx Hay varios prefijos de instruccion del tipo REP que se pueden usar con las instrucciones de comparacion de cadenas.5. si las comparaciones paran porque se vuelve cero. ARREGLOS CMPS B CMPS W el byte [ES:EDI] compara el byte [DS:ESI] y ESI = ESI ± 1 EDI = EDI ± 1 compara la palabra [DS:ESI] [ES:EDI] ESI = ESI ± 2 EDI = EDI ± 2 compara la dword [DS:ESI] y [ES:EDI] ESI = ESI ± 4 EDI = EDI ± 4 y la word CMPS D la dword SCASB compara AL y [ES:EDI] EDI ± 1 SCAS W SCAS D compara AX y [ES:EDI] EDI ± 2 compara EAX y [ES:EDI] EDI ± 4 Figura 5. Sin embargo. ¿Por qué uno no solo ve el registro FLAGS almacenara el estado que terminá la repeticián.11: Comparación de las instrucciones de cadena 5. La Figura 5.

bss array segment . _asm_strlen. 1 0 0 eax. edi apunta ahora a encontró 1 2 en array Figura 5. INSTRUCCIONES DE ARREGLOS/CADENAS segment . apuntador al inicio . void asm_copy( void * dest. src .apuntador del bufer para copiar desde . _asm_strcpy segment . array ecx. unsigned sz). _asm_find. nómero de elementos . sz .text .text cld mov mov mov lp: scasd je 115 resd 1 0 0 edi. REPZ repite la instrucciínn mientras la bandera Z este fija o míxi. código a jmp found: sub . 1 2 . const void * src.2.nómero de bytes a copiar .12: Ejemplo de búsqueda REPE. codigo a onward: found lp ejecutar si no onward edi.5. . nómero a buscar del arreglo loop . 4 ejecutar si se se encontro .apuntador del bófer para copiar a .13: Prefijos de instrucción REPx conocidas de C. copia un bloque de memoria .mo ECX veces REPNE. parómetros: . repite la instrucciín mientras la bandera Z este borrada o míximo REPNZ ECX veces Figura 5.memory. función _asm_copy .asm ---------------------------------------global _asm_copy. --------------------------------------------------. prototipo de C . dest .

esi = dirección del bUfer para . edi = direccion del búfer para . código jmp equal: onward: 0 g 1 d óo c %define dest %define src %define sz push mov mov mov cld rep pop pop leave ret function asm find si los bloques son iguales Figura 5. src edi. blockl edi. borrar la bandera de direccion movsb edi esi . size cmpsb equal para ejecutar onward para ejecutar dirección del primer bloque dirección del segundo bloque tamaño del bloque en bytes repita mientras la bandera Z estó fija Si Z estó fija.116 CAPÍTULO 5. block2 ecx. se definen algunos símbolos útiles [ebp+8 ] [ebp+1 2 ] [ebp+16] _asm_copy: enter push esi edi esi. los bloque son iguales si los bloques no son igual . sz . ARREGLOS segment .text cld mov mov mov repe je esi.14: Comparando bloques de memoria ahora. ejecute movsb ECX veces O o . dest ecx. ecx = nUmero de bytes a copiar .

apuntador al lugar dánde buscar target . al tiene el valor a buscar eax. target src sz .1) eax.tamaño en bytes de dánde se busca valor de retorno si target se encuentra. pero se empuja en la pila como una palabra doble.0 edi . %define src [ebp+8 ] %define target [ebp+1 2 ] %define sz [ebp+16] asm find: enter push mov mov mov cld 0. unsigned sz). Si la bandera cero se fija. ecx. apunta a la primera ocurrencia de target si no retorna NULL OBSERVE: target es un valor de un byte. Si no se encontró. parámetros src . encontró el valor .valor del byte a buscar sz . edi.2.5. El byte se almacena en los 8 bits inferiores. 0 short quit . Si se encuentra retorna (DI . edi eax . entonces se . char target. busca hasta que ECX == 0 or [ES:EDI] == AL repne scasb je found_it mov jmp found_it: mov dec eax. retorna un apuntador NULL pop leave ret quit: edi . INSTRUCCIONES DE ARREGLOS/CADENAS 117 Busca en la memoria un byte dado void * asm_find( const void * src.

src . parámetro . copia una cadena . busca un 0 para terminar . so length is FFFFFFFE . 0 0 edi = apuntador a la cadena usa el mayor valor posible en al = 0 l a . ARREGLOS . not FFFFFFFF .apuntador a la cadena origen %define dest [ebp + 8 ] %define src [ebp + 1 2 ] _asm_strcpy: enter 0 .ecx mov edi. parámetros . retorna el tamaño de una cadena . dest . ecx pop edi leave ret .ECX mov eax.apuntador a la cadena de destion . numero de caracteres en la cadena (sin contar el 0 final) (en EAX) %define src [ebp + 8 ] _asm_strlen: enter push edi mov mov xor cld repnz scasb edi. src . OFFFFFFFFh . function _asm_strlen . const char * src). src . . a l . length = OFFFFFFFEh .0FFFFFFFEh sub eax. funcion _asm_strcpy . 0 121 push esi 122 push edi 123 123 124 .118 CAPÍTULO 5.apuntador a la cadena . .ECX. unsigned asm_strlen( const char * ). valor de retorno: . repnz will go one step too far. void asm_strcpy( char * dest. src ecx. dest mov esi. .

/* busca un byte en la cadena */ scanf(” %c%*["\nj”. asm_strlen(st1 )). st2 ). st1 [0] = 0. char st2[STR_SIZE] char * st. printf (” len =%u\n”. if ( st ) printf (”Encontrado en : %s\n” .memory. almacena AL e inc di . unsigned ) __attribute__((cdecl)). unsigned asm_strlen( const char * ) __attribute__ ((cdecl )). scanf(” %s”. st1. INSTRUCCIONES DE ARREGLOS/CADENAS 119 125 126 127 128 129 130 132 131 132 133 134 cld cpy_loop: lodsb stosb or al. si no pasa terminando 0 . st2 ). /* copy meaningful data in string */ printf (” %s\n”. continúa leave ret -------------------------------------------------. STR_SIZE). printf (” Digite un caracter : ” ). const char * ) __attribute__((cdecl)). char ch. fija las banderas de condición . printf (” Digite una Cadena:”).h> #define STR_SIZE 30 /* prototipos */ void asm_copy( void *. &ch). al jnz cpy_loop pop pop edi esi . void asm_strcpy( char *.5. stl). Caraga AL e inc si .asm --------------------------------------------------_____________________________ memex. else printf (”No encontrado\n”). st ). unsigned ) __attribute__((cdecl)). asm_strcpy( st2. stl). const void *. st = asm_find(st2.2. void * asm_find( const void *. ch. int main() { char st1 [STR_SIZE] = ”test string” . . STR_SIZE). asm_copy(st2. /* copia todos los 30 caracteres de la cadena*/ printf (” %s\n”.c _________________________________ #include <stdio. char target .

1 +0 x 2.1 +2 x 10. debe ser posible representar números no enteros en otras bases como en decimal. En general divida el número decimal en dos partes: entero y fracciún. En decimal.25 + 0.120 CAPÍTULO 5. b.0112 = 4 + 2 + 0.1. solo se trataron los valores enteros.1012 = 1 x 2. 6.625 Esta idea se puede combinar con los metodos para enteros del capítulo 1 para convertir un número. La parte fraccionaria se convierte usando el metodo descrito abajo: Considere una fracción binaria con los bits etiquetados como a.. Convierta la parte entera usando a binario usando los metodos del capútulo 1. 0. abcdef .375 Convertir de decimal a binario no es muy difícil.125 = 6..1... La representaciún binaria del nuevo número será: a.1.. 110. } Capítulo 6 memex. Multiplique el número por dos. Representación de punto flotante Nómeros binarios no enteros Cuando se discutieron los sistemas numéricos en el primer capítulo.. entonces el número se ve como: 0 . ARREGLOS return 0.123 = 1 x 10. 0. bcdef .2 + 1 x 2.2 + 3 x 10.3 = 0. los dígitos a la derecha del punto decimal tienen asociados potencias de 1 0 negativas. Obviamente.c Punto flotante 6. c. .3 No es sorprendente que los números binarios trabajen parecido..

2 x 2 = 1.5625 a binario. Esto significa que . El metodo para cuando la parte fraccionaria es cero.4 x 2 0.6 0. PUNTO FLOTANTE 0. considere convertir 23. cdef . se encuentra con un bucle infinito.5 x 2 = 0.85 a binario Observe que el primer bit está ahora en el lugar del uno. Este procedimiento se puede repetir hasta los bits que se deseen encontrar.25 = 0.2: Convertir 0. Es facil convertir la parte entera (23 = 101112).7 x 2 0.121 CAPÍTULO 6.85 a binario.125 x 2 0.8 = 1.2 = 0.4 = 0..125 first bit =1 0.25 x 2 0.4 = 0.8 = 1.1: Convertir 0..5625 x 2 = 1.5625 a binario 0.5 = 1. Reemplace a con y obtiene: 0 .7 = 1.1 muestra un ejemplo real que convierte 0. Como otro ejemplo.4 x 2 0. 0 y multiplique por dos otra vez para obtener: b.2 muestra el comienzo de este calculo. La Figura 6.85)? La Figura 6.6 x 2 0.85 x 2 0. bcdef .8 x 2 Figura 6.6 = 1.. Ahora el segundo bit (b) esta en la posición del uno.0 second bit third bit fourth bit =0 =0 =1 Figura 6.8 x 2 0.. Si uno mira cuidadosamente los números. pero ¿Que pasa con la parte fraccionaria (0.

tomúticamente.2. otros compiladores usan la precisión doble para double y long double. Este formato se usa en la mayoría (pero no todos) los computadores hechos hoy día. uno puede ver que 0. Así 23.85 no se puede representar exactamente en binario usando un número finito de bits (tal como 3 no se puede representar en decimal con un número finito de dígitos).85 23 22 en un binario periódico (opuesto 0 a los decimales f periódicos en base 10). 1 Hay un patron de los números calculados.. x 2 1 0 0 (Donde el exponente (100) esta en binario). De hecho.1 1 0 1 1 0 2 . ssssssssssssssss x 2 eeeeeee Donde 1. (Esto es permitido por ANSI C). 0 1 1 1 1 1 0 1 1 0 0 1 1 0 0 1 1 0 . Sin embargo. Solo se puede almacenar una aproximacion a 23. 1 1 0 1 1 0 0 1 1 0 0 1 1 0 . todos los datos en el co.ganizacion internacional que ha diseñado formato binarios específicos para almacenar números de punto flotante. 6.85 o 1 0 1 1 1 .85 no se pueden almacenar exactamente en estas variables. sssssssssssss es la mantisa y eeeeeeee es el exponente. las variables float y double en C son almacenados en binario. . Una consecuencia importante del cúlculo anterior es que 23.procesador en sú mismo estúan en esta precisiúon. Mirando en el patron. Así. Un número de punto flotante normalizado tiene la forma: 1. Cuando se almacenan en memoria desde el coprocesador se convierte a precisioún simple o doble au. valores como 23. Por ejemplo el coprocesador numerico (o matematico) de Intel (que está empotrado en todas las CPU desde el Pentium) lo usa. usando potencias de dos. REPRESENTACIÓN DE PUNTO FLOTANTE 31 30 122 0.85 = 1 0 1 1 1 .6.1. A menudo es soportado por el hardware de computador en sú mismo. Este formato usa la notaciún científica (pero en binario..85 = 0 . El IEEE define dos formatos con precisioún diferentes: precisiúon simple y doble.. Como muestra este capítulo. la precisiúon simple es usada por las variables float en C y la precisiún doble es usada por la variable double. 27 La precisiún extendida usa un formato general ligeramente e 27Algunos compiladores (como Borland) los tipos long double usa esta precisión extendida. los números de puntos flotante se almacenan con un formato consistente. . Representación IEEE de punto flotante El IEEE (Institute of Electerical and Electronic Engineer) es una or.1. Por ejemplo 23. El coprocesador matemúatico de Intel utiliza un tercer nivel de mayor precisiún llamado precisión extendida. no de diez). 2 se almacenara como: 1 .1 1 0 1 1 0 2 .85 Para simplificar el hardware.

1 = negativo e exponente polarizado (8-bits) = verdadero exponente + 7F (127 decimal). La CPU no conoce cuál es la 0100 0001 1011 1110 1100 1100 1100 11002 = 41BECCCC16 interpretacioén correcta. Uno debería tener en cuenta ¿Como se podría almacenar 23. así que el se pueden interpretar de exponente es 7F + 4 = diferentes maneras dependiendo qué hace un pro. El bit 31 determina el signo del nuúmero como se muestra.85 no se puede representar exactamente como arriba.85. este uno no se almacena. Esto permite el almacenamiento de un bit adicional al final y así se incrementa un poco la precision. Los valores 00 y FF tienen significado especial (vea el texto).s bit de signo .0 = positivo. Ellos usan la representaciúon de magnitud y signo. Finalmente la fraccion es (recuerde el uno de adelante esta grama con ellos. f fraccion . Este nímero es muy cercano a 23. ellos representan. REPRESENTACIÓN DE PUNTO FLOTANTE 123 Esto no es exactamente 23. se almacena la suma del exponente y 7F en los bits 23 al 30. Hay varias peculiaridades del formato. 23.831 6 . en la mantisa. Este exponente polarizado siempre es no negativo. La parte fraccionaria se asume que es normalizada (en la forma 1. pero no es exacto. ellos y la fracciín han sido subrayados y los bits se han agrupado en representan. Ya que el primer bit es siempre uno.85 (ya que es un binario periodico).1. Figura 6.sssssssss). en C. ahora el exponente verdadero es 4.los 23 primeros bits despues de 1. El exponente binario no se almacena directamente. Presicion simple IEEE El punto flotante de precisiúon simple usa 32 bits para codificar el nuúmero. La Figura 6. pero como un nibles): entero. Esta idea se conoce como la representación oculta del uno. En su lugar.3: Precision simple de la IEEE diferente que los formatos float y double de la IEEE y no serán discutidos aca. uno encuentra que es aproximadamente. 6. Los números de punto flotante no usan la representaciúon en complemento a 2 para los nuúmeros negativos. Los números de punto flotante son almacenados en una forma mucho m ú a s complicada que los enteros. Como el oculto). Ya que el bit del 0 000 0000 0 001 0010 0000 0000 0000 0000 . Normalmente son exactos los primeros 7 dígitos decimales.85? Primero es positivo. Colocando todo esto unido (para ayudar a aclarar las némero de punto flotante de diferentes secciones del formato del punto flotante. Si uno convierte el nímero anterior a decimal. Actualmente. el bit de signo precisién simple. así el que los bytes 41 BE CC CD bit de signo es 0.3 muestra la forma basica del formato de precisiún simple del IEEE.

PUNTO FLOTANTE e = 0 and f = 0 e = 0 and f = 0 e = FF and f = 0 e = FF and f = 0 denota el número cero (que no puede ser nor- malizado ) Observe que hay un +0 y -0.11111.124 CAPÍTULO 6. sumar dos infinitos. Un resultado indefinido se produce por una operacion no vúlida como tratar de encontrar la raúz cuadrada de un nuúmero negativo. etc.39).85.0012 x 2. Sin embargo. Hay infinitos positivo y negativo.85? solo cambie el bit de signo: C1BECCCD ¡no tome el complemento a 2 ! Ciertas combinaciones de e y f tienen significado especial para los float IEEE. el exponente es fijado a 0 (ver la tabla 6. ¿Como se podría representar -23.001 x 2.1755 x -35 10 ) to 1. Conocido como NaN (Not a Number). En la forma normalizada dada. Estos se discuten en la proxima seccion. La Tabla 6. Cuadro 6. 0 1 0 0 1 2 x 2 -127.65 30 x 10. Los números de precisiún simple estan en el rango de 1. Un infinito se produce por un desborde o por una division por cero. considere el número 1.1 2 9 es entonces: .1 2 7 (todos los bits son almacenados incluyendo el 1 a la izquierda del punto decimal). el exponente es muy pequeño. El último bit se redondea a 1. Para almacenar este número. Convirtiendo esto a decimal el resultado y que es una aproximaciúon ligeramente mejor de 23. Números sin normalizar Los nuúmeros sin normalizar se pueden usar para representar nuúmeros con magnitudes mas pequeñas que los normalizados (menores a 1 .85 podría ser representado como 41 BE CC CD en hexadecimal usando precision simple.. Por ejemplo.1) y la funciúon de la mantisa completa del nuúmero escrito como un producto con 2 . x 21 2 7 (w 3.1 describe estos valores especiales.1 2 9 ( 1..4028 x 1035). se puede representar de una forma no normalizada como: 0 . Asú 23.1 2 6 (w 1.1: Valores especiales de f y e extremo izquierdo m a ú s significativo que fue truncado de la representacioún exacta es 1. denota infinito (ro). La representacion de 1. 0 x 2 -126). denotes un número sin normalizar. denota un resultado indefinido.0 x 2 .

6.1. REPRESENTACIÓN DE PUNTO FLOTANTE
63 62 ______________ 52 51 _______________________________________ O se f Figura 6.4: Precision doble del IEEE doble precisión IEEE

125

La doble precisiúon IEEE usa 64 bits para representar nuúmeros y normalmente son exactos los 15 primeros dúgitos decimales significativos. Como muestra la Figura 6.4, el formato basico es muy similar a la precision simple. Se usan mús bits para el exponente (ll) y la fracciún (52) que para la precisioún simple. El gran rango para el exponente tiene dos consecuencias. La primera es que se calcula como la suma del exponente real y 3FF (1023) (no 7F como para la precision simple). Segundo, se permite un gran rango de exponentes verdaderos (y asú usa un gran rango de magnitudes). Las magnitudes de precision doble van de 1 0 - 3 0 8 hasta 1 0 3 0 8 aproximadamente. En el campo de la fraccion el responsable del incremento en el número de dúgitos significativos para los valores dobles. Como un ejemplo, considere nuevamente 23.85 otra vez. El exponente polarizado sera 4 + 3FF = 403 en hexadecimal. Asú la representacion doble sería:
0100 0000 0011 0111 1101 1001 1001 1001 1001 1001 1001 1001 1001 1001 1001 1010

O 40 37 D9 99 99 99 99 9A en hexadecimal. Si uno convierte esto a decimal uno encuentra 23.8500000000000014 (¡hay 12 ceros!) que es una aproximaciúon mucho mejor de 23.85. La precisioún doble tiene los mismos valores especiales que la precisiúon simple. 28 Los números no normalizados son muy similares tambien. La principal diferencia es que los números dobles sin normalizados usan 2 - 1 0 2 3 en lugar de 2 -127.

6.2.

Aritmetica de punto flotante

La aritmúetica de punto flotante en un computador diferente que en la matemúatica continua. En la matemúaticas, todos los nuúmeros pueden ser considerados exactos. Como se muestra en la secciúon anterior, en un computador muchos nuúmeros no se pueden representar exactamente con un nuúmero finito de bits. Todos los cúalculos se realizan con una precisioún limitada. En los

28La única diferencia0 000 0 001 0010 0000 0000 0000 es que0000 para los valores de0000 infinito e indefinido, el exponente polarizado es 7FF y no FF.

6.2. ARITMÉTICA DE PUNTÓ FLOTANTE

126

ejemplos de esta secciún, se usaran números con una mantisa de 8 bits por simplicidad.

6.2.1.

suma

Para sumar dos nuúmeros de punto flotante, los exponentes deben ser iguales. Si ellos, no son iguales, entonces ellos se deben hacer iguales, desplazando la mantisa del nuúmero con el exponente m ú a s pequenño. Por ejemplo, considere 10,375 + 6,34375 = 16,71875 o en binario:
1.100110 1,1001011 x 2
2

x 23 +

Estos dos números no tienen el mismo exponente así que se desplaza la mantisa para hacer iguales los exponentes y entonces sumar:
1.100110
3

x 23 +

0,1100110 x 2 10,0001100 x 23

Observe que el desplazamiento de 1,1001011 x 2 2 pierde el uno delantero y luego de redondear el resultado se convierte en 0,1100110 x 2 3. El resultado de la suma, 10,0001100 x 23 (o 1,00001100 x 24) es igual a 1 0 0 0 0 , 1 1 0 2 o 16.75. Esto no es igual a la respuesta exacta (16.71875) Es solo una aproximacion debido al error del redondeo del proceso de la suma. Es importante tener en cuenta que la aritmúetica de punto flotante en un computador (o calculadora) es siempre una aproximaciún. Las leyes de las matemúaticas no siempre funcionan con nuúmeros de punto flotante en un computador. Las matematicas asumen una precisiún infinita que un computador no puede alcanzar. Por ejemplo, las matematicas enseñan que (a+b) — b = a; sin embargo, esto puede ser exactamente cierto en un computador.

6.2.2.

Resta

La resta trabaja muy similar y tiene los mismos problemas que la suma. Como un ejemplo considere 16,75 — 15,9375 = 0,8125: Subtraction works very similarly and has the same problems as addition. As an example, consider 16,75 — 15,9375 = 0,8125:
1.110

x 24

— 1,1111111 x 23 Desplazando 1,1111111 x 23 da (redondeando arriba) 1,0000000 x 24 x 24 — 1,0000000 x 24 0,0000110 x 24 29 0000110 x 2 = 0,112 = 0,75 que no es exacto.
1.110

0,

6.2.3.

Multiplicación y división

29Una

raíz de una función es un valor x tal que f (x) = 0

6.3. EL COPROCESADOR NUMÉRICO

127

Para la multiplicacion, las mantisas son multiplicadas y los exponentes son sumados. Considere 10,375 x 2,5 = 25,9375:
1,100110

x 23 x 1,0100000 x 21

10100110 + 10100110 1,10011111000000 x 24

Claro esta, el resultado real podría ser redondeado a 8 bits para dar:
1 , 1 0 1 0 0 0 0 x 2 4 = 1 1 0 1 0 ,0 0 0 2 = 26

La division es mas complicada, pero tiene problemas similares con errores de redondeo.

6.2.4.

Ramificaciones para programar

El principal punto de esta seccion es que los calculos de punto flotante no son exactos. El programador necesita tener cuidado con esto. Un error comín que el programador hace con nímeros de punto flotante es compararlos asumiendo que el calculo es exacto. Por ejemplo considere una funcion llamada f(x) que hace un calculo complejo y un programa esta tratando de encontrar las raíces de la funciín. 4. Uno podría intentar usar la siguiente instruccion para mirar si x es una raíz:

if ( f(x) == 0 . 0 )
¿Pero si f(x) retorna 1 x 10 3 0 ? Esto probablemente significa que x es una muy buena aproximaciín a una raíz verdadera; sin embargo, la igualdad será falsa. Puede no haber ningín valor de punto flotante IEEE de x que retorne cero exactamente, debido a los errores de redondeo en f(x). Un metodo mucho mejor sería usar:

if ( fabs(f(x)) < EPS )
Donde EPS es un macro definido a ser un valor positivo muy pequeño (como 1 x 10-10). Esto es cierto si f(x) esta muy cercano a cero. En general, comparar un valor de punto flotante (digamos x) con otro (y) use:

6.3.

if ( fabs(x — y)/fabs(y) < EPS ) Hardware

El coprocesador numerico

6.3.1.

Los primeros procesadores Intel no teman soporte de hardware para las operaciones de punto flotante. Esto no significa que ellos no podúan efectuar operaciones de punto flotante. Esto solo significa que ellas se realizaban por procedimientos compuestos de muchas instrucciones que no son de punto flotante. Para estos primeros sistemas, Intel suministraba un circuito integrado adicional llamado coprocesador matemático. Un coprocesador matematico tiene instrucciones de múaquina que realizan instrucciones de punto flotante mucho m a ú srúapido que usando procedimientos de software (en los primeros procesadores se realizaban al menos 10 veces mús rapido). El coprocesador para el

30 Sin embargo el 80486SX no tiene el un coprocesador integrado . Cada registro almacena 80 bits. C1. todos los nemonicos del coprocesador comienzan por F. C2 and C3. Los registros de punto flotante estan organizados como una pila. Solo las 4 banderas usadas en comparaciones serún estudiadas. Los nuúmeros existentes se empujan en la pila para dejarle espacio al nuevo nuúmero. . Para el 80286 era el 80287 y para el 80386 un 80387. Todos los nuúmeros nuevos se anñaden al tope de la pila. El coprocesador numúerico tiene ocho registros de punto flotante.2. El procesador 80486DX integro el coprocesador matemútico en el 80486 en sú mismo 30. Hay tambiúen un registro de estado en el coprocesador numúerico. Desde el Pentium. Estos emuladores se activan automúaticamente cuando un programa ejecuta una instrucciúon del coprocesador y corre un procedimiento que produce los mismos resultados que el coprocesador (mucho mús lento claro esta). C0. Recuerde que una pila es una lista LIFO (Last In Firist Oút).128 CAPÍTULO 6. Tiene varias banderas. Aún los primeros sistemas sin un coprocesador pueden instalar un software que emula el coprocesador matemúatico.. Instrucciones Para distinguir facilmente las instrucciones normales de la CPU de las del coprocesador. El uso de ellas se discutirú luego.3.. ST7. STO siempre se refiere al valoren el tope de la pila. 6. PUNTO FLOTANTE 8086/8088 fue llamado 8087. Los registros de punto flotante se usan diferentes que los registros enteros en la CPU. Los registros se llaman STO. todas las generaciones del 80X86 tienen un coprocesador matematico empotrado. Los nuúmeros de punto flotante se almacenan en estos registros siempre como números de 80 bits de precision extendida. ST1. sin embargo el todavúa se programa como si fuera una unidad separada.

. Hay tambiúen varias instrucciones que almacenan datos de la pila en memoria. su valor se saca de la pila. EL COPROCESADOR NUMÉRICO Carga y almacenamiento 129 Hay varias instrucciones que cargan datos en el tope de la pila de registro del coprocesador: FLD source Carga un nuúmero de punto flotante de la memoria en el tope de la pila. lo convierte a punto flotante y almacena el resultado en el tope de la pila. FSTP dest Almacena el tope de la pila en la memoria tal como FST. Algunas de estas instrucciones tambiúen sacan el nuúmero de la pila. El destino puede ser una palabra o palabra doble. sin embargo luego de que se almacena el nuúmero. El destino puede ser un nuúmero de precisiúon simple o doble o un registro de coprocesador.sador. FIST dest Almacena el valor del tope de la pila convertido a entero en memoria. FXCH STn intercambia los valores en ST0 y STn en la pila (donde n es el número del registro de 1 a 7). Cúomo se convierte el nuúmero de punto flotante a entero depende de algunos bits de la palabra de control del coprocesador. El tope de la pila se saca y el destino tambien puede ser una palabra cuadruple.3. excepto por dos cosas. Sin embargo las instrucciones FSTCW (Store Control Word) y FDLCW (load control word) se pueden usar para cambiar este comportamiento. FST dest Almacena el tope de la pila (ST0) en memoria. almacena un FLD1 FLDZ uno en el tope de la pila. El destino puede ser un nuúmero de precisiúon simple o doble o un registro del coprocesador. Hay otras dos instrucciones que pueden mover o quitar datos de la pila en sú misma.6. Este es un registro especial (no de punto flotante) que controla cúmo trabaja el coproce. marcando el registro como no usado o vacúo. la palabra de control se inicia tal que redondea al entero m ú a s cercano cuando se convierte a entero. Por defecto. source puede ser una palabra. FFREE STn libera un registro en la pila. palabra doble o una palabra cuúadruple. almacena un cero en el tope de la pila. La pila no sufre ninguún cambio. La source puede ser un número de precision simple doble o extendida o FILD source un registro del coprocesador Lee un entero de memoria. FISTP dest Lo mismo que FIST.

FADD src ST0 += src. Hay el doble de instrucciones de resta que de suma porque el orden de los operandos es importante. (¡a + b = b + a. se mueve al próximo dobule . src debe ser una palabra o una palabra doble en memoria. FIADD src ST0 += (float) src.text mov mov fldz lp: fadd add loop fstp qword [esi] esi. ST0 = 0 resq SIZE resq 1 Figura 6.5: Ejemplo sumar arreglo Suma y resta Cada una de las instrucciones de suma calcula la suma de ST0 y otro operando.130 CAPÍTULO 6. src puede ser cualquier registro del coprocesador o un nímero de precision simple o doble en memoria.bss array sum segment . ST0 += *(esi) . La Figura muestra un pequeño codigo que suma los elementos de un arreglo de dobles. PUNTO FLOTANTE segment . el resultado siempre se almacena en un registro del coprocesador. Por cada instruccioín. ST0 dest += ST0. uno debe especificar el tamanño del operando de memoria. El destino puede FADDP dest. array . hay una alterna que resta en el orden inverso. Esas instrucciones al reves todas culminan con R o RP. ST0 ser cualquier registro del coprocesador. pero a — b = b — al). . suma un entero con ST0. SIZE esi. 8 lp qword sum . De otra forma el ensamblador no conocería si el operando de memoria era un float (dword) o double (qword). En las líneas 10 y 13. El destino puede ser cualquier registro del coprocesador FADDP dest o dest += ST0 entonces se saca de la pila. FADD dest. alamcena el resultado en sum ecx.

.

STO ST0 *= src. FSUBR src FSUB dest. dest = ST0 . ST0 = (float) src . dest = ST0 . ST0 -= (float) src. PUNTO FLOTANTE ST0 -= src. dest -= ST0. Resta ST0 de un entero. STO FISUB src FISUBR src Multiplicación y división La instrucciún de multiplicación son totalmente analogas a las instrucciones de suma. La division por cero de un infinito. dest puede ser cualquier registro del coprocesador. src debe ser una palabra o palabra doble en memoria. Resta un entero de ST0. src debe ser una palabra o palabra doble en memoria. src puede ser cualquier registro del coprocesador o un nuúmero de precisiúon simple o doble en memoria.dest.ST0. dest puede ser cualquier registro del coprocesador.dest entonces sale de la pila.ST0. dest puede ser cualquier registro del coprocesador. Multiplica un entero con ST0. FMUL src FMUL dest. src debe ser una palabra o palabra doble en memoria. dest *= ST0 entonces sale de la pila. . dest puede ser cualquier registro del coprocesador. ST0 *= (float) src. STO FSUBR dest. src puede ser cualquier registro del coprocesador o un número de presicion doble o simple en memoria. No es sorprendente que las instrucciones de division son analogas a las instrucciones de resta. ST0 = src .13G FSUB src CAPÍTULO 6. dest -= ST0 entonces sale de la pila. STO FSUBP dest o FSUBP dest. dest puede ser cualquier registro del coprocesador. STO FMULP FIMUL src dest o FMULP dest. src puede ser cualquier registro del coprocesador o un nuúmero de presiciúon doble o simple en memoria. STO FSUBRP dest o FSUBRP dest. dest puede ser cualquier registro del coprocesador. dest *= ST0.

dest puede ser cualquier registro del coprocesador. compara ST0 y src. Las instrucciones de salto condicional usan el registro FLAGS. dest puede ser cualquier registro del coprocesador.6. STO FDIVP dest o FDIVP dest.3. Desafortunadamente no es posible que la CPU acceda a estos bits directamente. src puede ser un registro del coprocesador o un float o double en memoria. Estas instrucciones cambian los bits Co. Ci. STO FDIVR dest. entonces saca de la pila. Divide STO por un entero. src puede ser cualquier registro del coprocesador o un nímero de precisión simple o doble en memoria. dest /= STO entonces sale de la pila. STO = (float) src / STO. puede ser un FCOMP src compara ST0 y src. dest /= STO. dest = STO / dest. src debe ser una palabra o palabra doble en memoria. La formula de instrucciones FCOM hacen esta operaciún. C2 y C3 del registro de estado del coprocesador. Sin embargo es relativamente fúcil transferir los bits de la palabra de estado a los bits correspondientes del registro FLGS usando algunas instrucciones nuevas. luego sale de la pila. FCOM src . src puede ser cualquier registro del coprocesador o una nímero de precisiín simple o doble en memoria. STO FIDIV src FIDIVR src STO /= src . EL COPROCESADOR NUMÉRICO FDIV src FDIVR src FDIV dest. Divide un entero por STO. Comparaciones El coprocesador también realiza comparaciones de números de punto flotante. STO FDIVRP dest o FDIVRP dest. FICOMP src compara ST0 y (float) src. dest puede ser cualquier registro del coprocesador. dest puede ser cualquier registro del coprocesador. STO = src / STO. src puede ser una palabra o palabra dobel en memoria. STO /= (float) src . src puede ser un registro del coprocesador o un float o double en memoria. dest = STO / dest entonces sale de la pila. entonces saca de la pila dos veces. src debe ser una palabra o palabra doble en memoria. no el registro de estado del coprocesador. src puede ser una palabra o palabra doble entera en memoria. FCOMPP compara ST0 y ST1. FICOM src compara ST0 y (float) src. FTST compara ST0 and 0.

if ( x A . El Pentium Pro (y procesadores posteriores II y III) soportan 2 nuevos operadores de comparacion que directamente modifican el registro FLAGS de la CPU. FCOMIP src compara ST0 y src. La Figura 6 . La Figura 6. SAHF Almacena el registro AH en el registro FLAGS. FCOMI src compara ST0 y src. Instrucciones misceláneas Esta sección cubre otras instrucciones que suministra el procesador: FCHS ST0 = . ST0 = x .132 CAPÍTULO 6. Las líneas 5 y 6 transfieren los bits C0. src debe ser un registro del coprocesador.7 muestra una subrutina de ejemplo que encuentran el maximo de dos nómeros tipo double usando la instruccion FCOMIP. Este es el porque la línea 7 usa la instrucción JNA. entonces se saca de la pila. ST1 no se quita de la pila del .ST0 cambia el bit de signo de ST0 FABS ST0 = |ST0| Toma el valor absoluto de ST0 FSQRT ST0 = VSTO Toma la raíz cuadrada de ST0 FSCALE ST0 = ST0 x 2 LST1J multiplica ST0 por una potencia de dos rápidamente. LAHF Carga el registro AH con los bits del registro FLAGS. No confundan esta instruccion con la funcion de comparacion de enteros (FICOMP y FICOM). . código para parte de end_if entonces else_part: . fld fcomp fstsw sahf jna then_part: jmP qword [x] qword [y] ax else_part . 6 muestra un ejemplo cortico. C2 y C3 de la palabra de estado del coprocesador al registro FLAGS.6 : Ejemplo de comparación FSTSW dest Almacena la palabra de estado del coprocesador in memoria o en el registro AX. código para parte si no end_if: Figura 6 . si x no es mayor que y. Ci. vaya a else_part . PUNTO FLOTANTE . Los bits son transferidos tal que ellos son analogos al resultado de una comparación de dos enteros sin signo. src debse ser un registro del coprocesador. compara STO and y . mueve los bits C a FLAGS .

3. —b ± Vb2 — 4ac Xl. b. int main() { double a.c. root2). su valor es uútil para determinar las 3 posibilidades para la soluciúon: 1.c _______________________________ A continuación está la rutina en ensamblador.10g\n”.4. printf(”Enter a.10g%. EL CÓPRÓCESADÓR NUMÉRICO coprocesador . double b. Recuerde que la formula cuadrática calcula la solucion de una ecuacion cuadráticai: ax2 + bx + c = 0 La formula en sí misma da dos soluciones para x: x1 y x2.6. 133 6.3. b2 — 4ac = 0 Hay dos soluciones reale.b.3. . La Figure 6 . } _____________________________ quadt.----------2a La expresion dentro de la raíz cuadrada (b2 — 4ac) es llamada discriminante. 6. &root2 ) ) printf (” roots: %. 2. &b. rootl. ____ quadt. double *). b. else printf (”No real roots\n”). scanf(” %lf%lf%lf”. 3.asm -------------------------------------------------función quadratic Halla la solución de la ecuación cuadrótica: a*x" 2 + b*x + c = 0 Prototipo de C: int quadratic( double a. rootl. ------------------------------------------------. Ejemplos Fórmula cuadrática El primer ejemplo muestra cúmo se puede programar la formula cuadrática en ensamblador.3. double c. 8 muestra un ejemplo de como usar esta instruccion. &c). double. double *. if (quadratic( a.c #include <stdio.quad. c. b2 — 4ac < 0 Este es un pequenño programa en C que usa la subrutina en ensamblador. return 0.h> int quadratic( double. &a. X2 = ----------. b2 — 4ac > 0 Hay dos soluciones complejas. root2 . c: ”). Hay solo un real en la solucion degenerad. double. &root1 .

Coeficientes de la ecuación cuadrótica (ver arriba) rootl .134 CAPÍTULO 6. double *root2 ) Parómetros: a. 16 ebx .Apuntador al double que almacena la segunda raóz Valor de retorno: devuelve 1 si las raóces son reales si no 0 /define a /define b /define c /define rootl /define root2 /define disc /define one_over segment . esp sub push _quadratic dw -4 qword qword qword dword dword qword qword [ebp+8 ] [ebp+l6 ] [ebp+24] [ebp+32] [ebp+36] [ebp-8 ] [ebp-l6 ] _2 a esp. debe guardar el valor original de ebx . c . b.Apuntador al double que almacena la primera raíz root2 .data MinusFour segment . PUNTO FLOTANTE double * rootl. asigna 2 doubles (disc & one_over_2 a) .text global _quadratic: push mov ebp ebp.

b pila: (-b . -4 pila: c. 0 ebx ÉSCJTl o'hTi valor de retorno es 0 .6. EL COPROCESADOR NUMÉRICO fild fld fld fmulp fmulp fld fld fmulp faddp ftst fstsw sahf jb fsqrt fstp fldl fld fscale fdivp fst fld fld fsubrp fmulp mov fstp fld fld fchs fsubrp fmul mov fstp mov jmp solutions: mov quit: pop mmr 135 word [MinusFour] a c stl stl b b stl stl ax pila -4 pila: a. stl . b.3. disc . stl . 0 pila: a. if disc < 0 . rootl qword [ebx] b. ebx. l.0 ) = 2 *a. l/(2 *a) pila: (-b + disc)/(2 *a) almacena en *rootl pila: b pila: disc. no hay soluc pila: sqrt(b*b .b. l short quit almacena y saca de la pila pila: l. root2 qword [ebx] eax. -4*a*c pila: b*b. -4 pila: a*c.4*a*c) disc a stl one_over_2 a b.4*a*c prueba con 0 no_real_solutions . l/(2 *a) pila: disc. stl . disc . b. l pila: l/(2 *a) pila: l/(2 *a) pila: b. b pila: -disc . -4 pila: -4*a*c pila: b. ebx. l/(2 *a) pila: disc . b pila: -disc. a. one_over_2 a .disc)/(2 *a) almacena en *root2 valor de retorno es l eax. -4*a*c pila: b*b . 0 pila: a * 2 "(l.

text global _read_doubles extern _fscanf /define SIZEOF_DOUBLE B .data format db "/lf". a[i ]). Sigue un pequeño programa de prueba en C.5. (use la ó redireccion para leer desde un archivo.c ___________________________________ A continuaciúon la rutina en ensamblador --. MAX). i++ ) printf (” %3d%g\n”. n . i < n. PUNTO FLOTANTE pop ebp ret quad.asm segment . Leer arreglos de archivos Este ejemplo. int ). for ( i=0. #define MAX 100 int main() { int i .136 CAPÍTULO 6. es una rutina en ensamblador que lee doubles de un archivo. a. n = read_doubles(stdin .) */ #include <stdio.h> extern int read_doubles( FILE *.3.asm 6.c ___________________________________ /* * Este programa prueba el procedimiento en ensamblador bit read-doubles () * Lee doubles de stdin . i. return 0. O . double *.read. double a[MAX]. _____________________________ readt. format for fscanf() segment . } _____________________________ readt.

1 2 edx .esp esp. ARRAY_SIZE short quit si edx < ARRAY_SIZE? si no. ARRAYP edx.3.apuntador FILE pointer a leer desde (se debe abrir para entrada) arrayp . guarda edx . TEMP_DOUBLE eax dword format FP _fscanf esp. restaura edx .6.8 ] 137 función _read_doubles prototipo de C: int read_doubles( FILE * fp.apuntador a un arreglo de doubles para leer en el array_size . push &TEMP_DOUBLE . salir del bucle llama a fscanf() para leer un double en TEMP_DOUBLE fscanf() podría cambiar edx y así guardarlo push lea push push push call add pop edx eax. double * arrayp. Esta función lee dobles de un archivo de texto hasta EOF o hasta que se llene el arreglo Parámetros: fp . SIZEOF_DOUBLE esi esi. int array_size ). edx define un double en la pila guarda esi esi = ARRAYP edx = índice del arreglo (inicialmente en 0 ) edx. push &format . push el apuntador . EL COPROCESADOR NUMÉRICO “/„define “/„define “/„define “/„define FP ARRAYP ARRAY_SIZE TEMP_DOUBLE dword [ebp + 8 ] dword [ebp +12] dword [ebp +16] [ebp .numero de elementos en el arreglo Valor de retorno: número de doubles almacenado en el arreglo (en EAX) _read_doubles: push mov sub push mov xor while_loop: cmp jnl ebp ebp.

y la trunca en lugar de redondearla. Si ellos son ambos 1. Altera la palabra de control de coprocesador tal que cuando almacena la raíz cuadrada como un entero. Almacena los primos que ha encontrado en un arreglo y solo divide por los primos que ha encontrado antes.asm . en lugar de cada número impar. restaura esi . Esta implementación es mús eficiente que la anterior. salir del bucle copia TEMP_DOUBLE en ARRAYP[edx] (Los ocho bytes del dobule son copiados por las dos copias de 4 bytes) mov mov mov mov inc jmp eax.3. Si ellos son ambos 0 (el defecto) el coprocesador redondea cuando convierte a entero. Esto es controlado por los bits 10 y 11 de la palabra de control. Estos bits se llaman los bits RC (Raunding Control). para encontrar nuevos primos. primero copia los 4 bytes inferiores quit: pop mov mov pop ret .6. [esi edx whil esi eax. el coprocesador trunca las conversiones enteras. Hallar primos Este último ejemplo halla números primos una vez más. A continuacion el programa en C: fprime. Observe que la rutina se cuida de guardar la palabra de control original y restaurarla antes de retornar. Otra diferencia es que calcula la raíz cuadrada del número para buscar el proximo primo a determinar en que punto parar la búsqueda de factores. 1 jne short quit . [esi eax.138 CAPÍTULO 6.c . PUNTO FLOTANTE cmp eax. ebp read. esp. si fscanf retorno 1 ? no. . almacena el valor de retorno en eax 6.

i+1.h> #include <stdlib. i++ ) printf (” %3d%d\n”. unsigned n ). int main() { int status. } return status } . max). if ( a ) { find_primes (a.? ”). /* imprime losU 20 ltimos primos encontrados */ for( i = ( max > 20 ) ? max — 20 : 0. status = 1. } else { fprintf (stderr . unsigned i.3. scanf(” %u”. max). printf (”¿aCuntos primos desea encontrar Ud. int * a.h> /* * 0 funcin find-primes * Halla los únmeros primos indicados * aParmetros: * a — arreglo que almacena los primos * n — acuntos primos encontrar */ extern void find_primes ( int * a. max). a = calloc( sizeof(int ). EL COPROCESADOR NUMÉRICO 139 #include <stdio.6. i < max. free (a). status = 0. ”No se puede crera el arreglo de%u ints\n” . a[i]). unsigned max. &max).

[orig_cntl_wd] ax.arreglo que almacena los primos n_find . PUNTO FLOTANTE fprime.text _find_primes global function find_primes Encuentra el número indicado de primos Parámetros: array . unsigned n_find ) %define array %define n_find %define n %define isqrt %define orig_cntl_wd %define new_cntl _wd _find_primes: enter push push fstcw mov or mov mov mov mov mov 12. fija el redondeo de los bits a 1 1 (truncar) esi apunta a array array[0 ] = 2 array[1] = 3 ebx = guess = 5 n= 2 esi. el piso de la raíz cudrada de guess . que se añade al final del arreglo. guarda las posibles variables registro ebx esi word [orig_cntl_wd] ax. nueva palabra de control .cuántos primos encontrar Prototipo de C extern void find_primes( int * array. ax fldcw word [new_cntl_wd] . numero de primos a encontrar . palabra original de control . [array] dword [esi]. 0C00h mov [new_cntl_wd].140 CAPÍTULO 6. 5 dword [n]. 2 Este bucle externo encuentra un nuevo primo en cada iteración. toma la palabra de control actual . 2 dword [esi + 4]. A diferencia del primer programa de . Hace espacio para las variables locales .asm _____________________ segment .c _ A continuación la rutina en ensamblador: ___ prime2 .0 ebp + ebp + ebp ebp ebp ebp - 8 12 4 8 10 12 . 3 ebx.

[isqrt] short quit_factor_prime eax. dword [esi + 4*ecx] eax. inc n . ebx inc eax mov [n]. eax = array[ecx] . ebx edx. 1 ebx dword [esp] pop ebx dword [isqrt] 141 ecx se usa como índice del arreglo almacena guess en la pila carga guess en la pila del coprocesador . esta función no determina los primos dividiendo por todos los números impares. intenta el práximo primo . [n] eax. edx short quit_factor_not_prime ecx short while_factor . Solo divide por los nómeros primos que ya se han encontrado. EL COPROCESADOR NUMÉRICO primos. saca guess de la pila halla sqrt(guess) isqrt = floor(sqrt(quess)) Este blucle interno divide guess por los numeros primos ya encontrados hasta que encuentre un factor primo de guess (que significa que guess no es primo) o hasta que número primo a dividir sea más grande que floor(sqrt(guess)) while factor: mov cmp jnbe mov xor div or inc jmp eax. suma al final de arreglo . eax .6. [n_find] short quit_limit ecx. found a new prime ! quit_factor_prime: mov eax. edx dword [esi + 4*ecx] edx. (Esta es la razón por la cual ellos se han almacenado en el arreglo) while_limit: mov cmp jnb mov push fild fsqrt fistp while ( n < n_find ) eax. while ( isqrt < array[ecx] . [n] mov dword [esi + 4*eax]. && guess % array[ecx] != 0 .3.

restaura las variables de registro prime2 . 2 jmp short while_limit quit_limit: fldcw word [orig_cntl_wd] pop esi pop ebx .asm leave ret . restaura la palabra de .142 quit_factor_not_prime: add ebx. intenta con el impar siguiente control .

STl = d2 1 2 3 segment .7: Ejemplo de FCOMIP .3.primer double dos argumentos double dl. O qword [d2 ] qword stl short stO qword short exit d 2 [dl] . STO = dl . ST1 =5 Figura 6 .segundo double Valor de retorno: El mayor de dl y d2 (en STO) /define dl ebp+B /define d2 ebp+l6 _dmax: enter fld fld fcomip jna fcomp fld jmp d2 _bigger: exit: leave ret O. ST0 = 2. ST0 = 5 . .text function _dmax retorna el mayor de Prototipo de C double dmax( double Parámetros: dl .text 2 Figura 6. no hace nada 5 4 5 6 7 8 fild dword [five] fld qword [x] fscale .data x dq five dw segment . EL COPROCESADOR NUMÉRICO 1 143 global _dmax segment . . STO = d2 pop d2 de [dl] . ST1 = 5 . ST0 = 2.6. STO = dl.75 * 32. convertido a formato 5 double r e g g i b _ la pila si d2 is max.8 : Ejemplo de FSCALE .75. double d2 ) 3 4 5 a 7 S Q 10 11 12 13 14 15 1a 17 1S 1Q 20 21 22 23 24 25 2a 27 2S d2 .

Para acceder a un elemento uno debe conocer la dirección de inicio de la estructura y el desplazamiento relativo desde el comienzo de la estructura. Estructuras Introducción Las estructuras se usan en C para agrupar datos relacionados en una variable compuesta. a diferencia de la sección de manejo de memoria virtual de cualquier libro de texto de sistemas operativos para la discusión del termino. 31vea 144 . 3. En ensamblador los elementos de una estructura se accederán de una manera parecida que los elementos de un arreglo.1. Esta tecnica tiene varias ventajas: 1.1. Esta característica es la que le permite a uno calcular la dirección de cualquier elemento conociendo la direccion de inicio del arreglo. Because of this each element of a structure must be explicitly specified and is given a tag (or name) instead of a numerical index. Los elementos de los arreglos reales son siempre del mismo tipo y mismo tamaño. Simplifica el paso de datos a las funciones. En lugar de pasar muchas variables separadas. el tamaño de los elementos y el índice del elemento que se desea. Incrementar la localidad del código. sin embargo. Desde el punto de vista del ensamblador una estructura se puede considerar como un arreglo con elementos de diferente tamaño. ellas se pueden pasar como uno una variable.1. 7. A structure’s elements do not have to be the same size (and usually are not). 31 2.Capítulo 7 Estructuras y C+—+ 7. Clarifica el código mostrando que los datos definidos en la estructura están relacionados íntimamente.

La Figura 7. Este macro toma dos parámetros. Tambien dicta que el primer elemento está al comienzo de la estructura (desplazamiento cero). La Figura 7. La norma ANSI dicta que los elementos de una estructura son colocados en memoria en el mismo orden que ellos son definidos en la estructura.2 muestra por cuatro. como se ve realmente la estructura S usando gcc.y) podría ser 2 segán la Figura 7.h llamado offsetof(). }. la CPU lee la memoria más rápido si palabra dóble si es divisible el dato condensa en el límite de una palabra doble. En el modo protegido de 32 bits. Alineamiento de la memoria /* entero de 2 bytes */ /* entero de 4 bytes */ /* float de 8 bytes */ Si uno usa el macro offsetof para encontrar el desplazamiento de y usando el compilador gcc uno encontrara que retorna 4 y no 2.1.145 CAPÍTULO 7.1 muestra como una variable de tipo S podría verse en la memoria del computador. el primero es el nombre de tipo de la estructura. El compilador inserta dos bytes no usados en la estructura para alinear y(y z) en un límite de palabra doble.1. el segundo del nombre del elemento al cual encontrarle el desplazamiento. double z. Cuando se usan estructuras definidas en C.2. inty. Este macro calcula y retorna el desplazamiento de cualquier elemento de una estructura.1: Estructura S los arreglos donde este desplazamiento se puede calcular por el índice del elemento de una estructura el compilador le asigna un desplazamiento. . ¿Por que? Porque gcc (y muchos otros Recuerde que una dirección compiladores) alinean por defecto las variables a los límites de palabras esta en los limites de una dobles. ESTRUCTURAS Y C++ Offset Element 0 2 6 y x z Figura 7. 7. Por ejemplo considere la siguiente estructura: struct S { short int x. Esto muestra por que es buena idea usar offsetof para calcular los desplazamientos en lugar de calcularlos uno mismo. Así el resultado de offsetof(S. Tambien define otro macro átil en el archivo de cabecera stddef.

Los compiladores de Borland y Microsoft. la siguiente línea: typedef short int unaligned_int __attribute__ ((aligned (1))). Los compiladores de C suministran maneras de especificar el alineamiento usado para los datos. 14. De esta forma S usaría el menor nímero de bytes posibles. Esto le dice al compilador que use el mínimo de espacio para la estructura. . sin embargo. Define un nuevo tipo llamado unaligned_int que se alinea en los límites de los bytes (sí. gcc podría colocar y en el desplazamiento 2. La Figura 7. sin la estructura solo se usa en ensamblador el programador puede determinar el desplazamiento el mismo.2. El compilador le permite a uno especificar el alineamiento de cualquier tipo usando una sintaxis especial. El 1 en el parámetro de aligned se puede reemplazar con otras potencias de dos para especificar otros alineamientos (2 para alineamiento de palabra.3 muestra como se podría escribir S de esta manera. el compilador de Borland podría crear una estructura como la de la Figura 7. el compilador gcc crea una estructura S que se ve como la Figura 7. La definicion del tipo de z tendría que ser cambiada para colocarlos una distancia de 6.terfazando C y ensamblador muy importante que los dos códigos esten de acuerdo en los desplazamientos de los elementos de la estructura. Por ejemplo. cada compilador lo hara a su manera.2: Estructura S Claro esta. Sin embargo si uno esta in. Si el elemento y de la estructura fue cambiado para ser un tipo unaligned_int. El compilador gcc tambien permite embalar una estructura.2. como se ha visto. ESTRUCTURAS DE CONTROL 146 x Offset Element 0 2 4 y 8 unused z Figura 7.1. Una complicación es que diferentes compiladores de C tienen desplazamientos diferentes de los elementos. Sin embargo z podría estar a una distancia de 8 ya que las palabras dobles tienen un alineamiento de palabra por defecto. Por ejemplo. 4 para el alineamiento de palabras dobles).2. El compilador gcc tiene un metodo flexible y complicado de especificar el Alineamiento. el paréntesis despues de __attribute__ se necesitan). soportan el mismo metodo de especificar el alineamiento usando la directiva #pragma. Sin embargo la norma ANSI C no específica como se hace esto y así.

3. palabra doble.4: Microsoft o Borland #pragma pack(1) La directiva anterior le dice al compilador que embale los elementos de estructuras en los límites de los bytes (sin relleno extra). Esto puede causar problemas ya que estas directivas se usan a menudo en los archivos de encabezamiento. /* entero de 4 bytes */ double z.3. El uno se puede reemplazar con dos.2. Esta directiva tiene efecto hasta que se sobreescriba por otra directiva. }. TRADUCIR ESTRUCTURAS DE CONTROL ESTÁNDARES 147 struct S { short int x. Esto puede conducir un error muy difícil de encontrar. La Figura 7.3: Estructura embalada usando gcc #pragma pack(push) /* guarda el estado de alineamiento */ #pragma pack(1) /* establece el alieamiento de byte */ struct S { short int double z.1. #pragma pack(pop) Figura 7. 7. int /* entero de 2 bytes */ /* entero de 4 bytes */ /* float de 8 bytes */ y. /* restaura el alineamiento original */ Estructura embalada usando Los campos de bit le permiten a uno especificar miembros de una estructura que usa un nímero especificado de bits. Hay una manera de evitar este problema. Los módulos de un programa podrían desordenar los elementos de las estructuras de otros módulos.4 muestra cómo se haría esto. El tamaño de los bits no . /* entero de 2 bytes */ int y. Figura 7. Microsoft y Borland tienen una manera de guardar el alineamiento y restaurarlo luego. cuatro. Si el archivo de encabezamiento se incluye antes que otros archivos de encabezamiento con estructuras. palabra cuadruple y parrafo respectivamente. Campos de bits x. ocho o diez y seis para especificar el alineamiento en una palabra. esas estructuras pueden estar diferente que las que tendrían por defecto. /* float de 8 bytes */ } __attribut^_((packed)).

La forma de la memoria física no es importante a menos que los datos se transfieran a o fuera de nuestro programa (lo cual es muy poco común con campos de bits). 32 Sin embargo. Por ejemplo. las partes de los campos ¡2 y ¡3 se colocaran en el lugar correcto. 8. Figura 7. el formato no es tan simple. 5 bits 3 bits 3 bits 5 bits 8 bits 8 bits f2l f1 f3l f2m f3m f4 La etiqueta ¡21 se refiere a los últimos cinco bits (los 5 bits menos significativos) del campo ¡2.148 CAPÍTULO 7.5 muestra un ejemplo. 11. si uno mira como se almacenan los bits en memoria.6. es una norma de la industria para discos duros y etc. los campos de la estructura S se verán en memoria. . La Figura 7. Microsoft y Borland) usaran este orden en los campos. Un campo de bits miembro está definido como un unsigned int o un int con dos puntos y luego el tamaño en bits. Un ejemplo es SCSI: 33 Una orden de lectura directa para un dispositivo SCSI se especifica enviando un mensaje de 6 bits al dispositivo con el formato especificado en la Figura 7. La dificultad de representar esto usando 3. Porque los bytes en un procesador little endian se invertirán en memoria. 10. Esto define una variable de 32 bits que se descompone en las siguientes partes: 8 bits 11 bits 10 bits 3 bits f4 f3 f2 f1 El primer campo estúa asignado a los bits menos significativos de esta palabra doble. Las líneas verticales dobles muestran los límites de los bytes. la norma ANSI/ISO C le da al compilador alguna flexibilidad de como se ordenan los bits exactamente. Si uno invierte todos los bytes. ESTRUCTURAS Y C++ struct S { unsigned f1 unsigned f2 unsigned f3 unsigned f4 }. La etiqueta ¡2m se refiere a los 5 bits mús significativos de ¡2. es común para dispositivos de hardware usar números impares de bits donde los campos de bits son útiles para representarlos. 33Small Computer System Interface. /* campo de 3 bits */ /* campo de 10 bits */ /* campo de 11 bits */ /* campo de 8 bits */ 32Actualmente. Sin embargo los compiladores populares (gcc. La dificultad ocurre cuando los campos de bits se salen de los límites del byte.5: Ejemplo campo de bits tiene que ser múltiplo de 8.

9 muestra otra definicion que trabaja con los tres compiladores. Esto es porque el compilador de Microsoft usa el tipo de campo de datos para determinar como colocar los bits. Ahora el compilador de Microsoft no necesita agregar ningín relleno ya que los 6 bytes son un míltiplo de una palabra 34 Los otros compiladores tambien trabajaran correctamente con este cambio. La Figura 7. El lector no debería entristecerse si encuentra la division anterior confusa. la definicion para SCSI_read_cmd no trabaja correctamente en el compilador de Microsoft. Ellos tienen que colocarse en este orden.7 muestra una definición que intenta trabajar con todos los compiladores.6: Formato de la orden leer de SCSI campos de bits es la dirección del bloque lógico que abarca 3 bytes diferentes en la orden. Un campo de 16 bits podría ser almacenado con el orden little endian por el compilador. Cuando esto se almacena en memoria en el orden little endian.3. Esto se puede solucionar haciendo todos los campos unsigned short. Ya que los bits estín definidos como de tipo unsigned.6 uno ve que el dato se almacena en el formato big endian.6) Para complicar las cosas mas. Si se evalía la expresion sizeof (SCSI_read_cmpl). Ahora. Se invita al lector a experimentar al respecto 7 5 5 4 3 2 1 0 34Mezclar . La parte potencialmente confusa esta en las líneas 11 a 14.2. Las dos primeras líneas definen un macro que retorna verdadero si el codigo se compila con los compiladores de Borland o Microsoft. los bits se colocan en el orden deseado. sin embargo esto no es el caso. De la Figura 7. TRADUCIR ESTRUCTURAS DE CONTROL ESTÁNDARES 149 Byte \ Bit 0 Codigo de operación (08h) 1 Unidad Lógica # msb de LBA 2 mitad de la dirección del bloque lógico 3 lsb de dirección del bloque lógico 4 Longitud de transferencia 5 Control Figura 7. ¡Es confusa! El autor a menudo encuentra menos confuso evitar los diferentes tipos de campos de bits produce un comportamiento muy confuso. el compilador rellena dos bytes al final de la estructura para tener un míltip.(Figura 7. La Figura 7.lo de palabras dobles.8 muestra como se colocan los campos en una entidad de 48 bits (los límites del byte estan una vez mas marcados con líneas dobles). los campos lba_msb y logicaLunit parecen invertidos. Se evitan todos los problemas usando campos de tipo unsigned char. La Figura 7. Microsoft C retornaría 8 no 6. Primero uno podría sorprenderse de ¿Por que los bits lba_mid y lba_lsb estan definidos separados y no en un solo campo de 16 bits? La razón es que el dato esta en big endian.

8: Ordenamiento de los campos de SCSI_read_cmd 5 bits lba_msb 8 bits opcode struct SCSI_read_cmd { unsigned char opcode.4.1. La rutina en ensamblador sería: 8 bits 8 bits 8 bits 8 bits 3 bits control transfer Jength lbaJsb lba_mid logicaLunit Figura 7. Por ejemplo considere como uno podría escribir una rutina en ensamblador que iniciaría con cero el elemento y de una estructura S. unsigned char control.7: Estructura del formato de la orden leer de SCSI campos de bits y en su lugar usar las operaciones entre bits para examinar y modificar los bits manualmente. ((packed)) #endif #if MS_OR_BORLAND # pragma pack(pop) #endif Figura 7. unsigned logicaLunit : 3. /* bits de la mitad*/ unsigned char lba_lsb. } #if defined (__GNUC__) „attribute. unsigned char lba_msb : 5. unsigned lba_mid : 8. /* bits de la mitad */ unsigned lba_lsb : 8.7. unsigned char transferJength . acceder a una estructura en ensamblador es muy parecido a acceder a un arreglo. unsigned lba_msb : 5. 7.1. unsigned control : 8. . Asumiendo que el prototipo de la rutina sería: void zero_y( S * s_p ). unsigned transferJength : 8. unsigned char lba_mid. ESTRUCTURAS 150 #define MS_OR_BORLAND (defined(__BORLANDC__) \ || defined (_MSC_VER)) #if MS_OR_BORLAND # pragma pack(push) # pragma pack(1) #endif struct SCSI_read_cmd { unsigned opcode : 8. unsigned char logicaLunit : 3. Usando estructuras en ensamblador Como se discutio antes.

[ebp + 8 ] mov dword [eax + y_offset]. 0 leave ret . Consulte su h> void f( int x ) { printf (” %d\n” . Es mucho mas eficiente pasar un apuntador a una estructura. La mayoría de los ensambladores (incluido NASM) tiene un soporte empotrado para definir estructuras en su codigo de ensamblador. Obviamente la estructura no se puede retornar en el registro EAX. Una solucion comín es que el compilador usa esto para internamente reescribir la funcion para que tome una estructura como paraímetro.9: Estructura alterna del formato de la orden leer de SCSI 1 2 3 4 5 6 7 8 %define y_offset 4 _zero_y: enter 0 . de la pila C le permite a uno pasar estructuras por valor a una funcion. todos los datos de la estructura se deben copiar en la pila y luego traídos por la rutina. Cada compilador maneja la situacion diferente. } Figura 7.x).7.10: Dos funciones f() ) 3 u O “O tn e d V in . ENSAMBLADOR Y C++ 151 } #if defined(_GNUC_) __attribute__ ((packed)) #endif Figura 7. Cuando se pasa por valor. } void f( double x { printf (” %g\n” . sin embargo esto la mayoría de las veces es mala idea.x). 0 mov eax. El apuntador se usa para colocar el valor de retorno en una estructura definida fuera de la rutina llamada.2. toma s_p (apuntador a la estructura) . C Tambien permite que un tipo de estructura se use como un valor de retorno.

_f __Fd. las reglas no son completamente arbitrarias. 7. Cuando se compila un archivo el compilador no tiene manera de saber si una funcion en particular estí sobrecargada o no. el encadenador producirá un error porque el encontrara dos definiciones para el mismo símbolo en los archivos objeto que esta encadenando. pero evita este error haciendo una manipulación de nombres o modificando el símbolo usado para etiquetar la funcion. int y. Manipulación de la sobrecarga de nombres C++ permite que se definan diferentes funciones (y funciones miembro de clases) con el mismo nombre.152 CAPÍTULO 7. así que manipula todos los nombres. El tipo de retorno de una funcion no hace parte de la firma de la fun. Esta secciúon asume un conocimiento básico de C++. El codigo en ensamblador equivalente definiría 2 etiquetas _f que obviamente sera un error. Observe que la funciín que toma un solo parámetro int tiene una i al final de la etiqueta manipulada (para DJGPP y Borland) y la que toma un argumento double tiene una d al final de la etiqueta manipulada.10 le podría asignar DJGPP la etiqueta _f_Fi y a la segunda funcion. DJGPP podría manipular el nombre para ser _f__Fiid y Borland a @f$qiid.2. La firma de una funciín esta definida por el orden y el tipo de sus parámetros. si dos funciones. ESTRUCTURAS Y C++ documentación para los detalles.10 de la misma manera y produce un error. Por ejemplo considere el codigo de la Figura 7. C++ usa un proceso de manipulacion mas sofisticado que produce dos etiquetas diferentes para las funciones. Por defecto C++ manipula los nombres de las funciones así no esten sobrecargadas. Por ejemplo Borland C++ podría usar las etiquetas @f$qi y @f$qd para las dos funciones de la Figura 7. Sin embargo algunas reglas necesitan ser modificadas. Solo funciones cuya firma es ínica se pueden sobrecargar. a la primera funcion de la Figura 7. Por ejemplo. Si tuviera una funcion llamada f con el prototipo. El nombre manipulado codifica la firma de la funcion. De alguna manera C usa la manipulaciún de nombres tambien.10. Si se definen dos funciones con el mismo nombre en C. double z).2.cion y no se codifica en el nombre manipulado. Esto evita errores de encadenamiento. Como uno puede ver. Desafortunadamente no estí normalizado címo se manejan los nombres en C++ y cada compilador trata los nombres a su manera. C++ usa el mismo proceso de encademanmiento que C. Ensamblador y C+—+ El lenguaje de programacion C++ es una extensiún del lenguaje C. se dice que la funcion esta sobrecargada.1. Añade un guion bajo a la etiqueta de la funcion de C cuando se crea la etiqueta para la funcion. estan definidas en C++ ellas producirán el mismo nombre manipulado y crearan un error de encadenamiento.10. Tambien algunas de las extensiones de C++ son faciles de entender con conocimiento del lenguaje ensamblador. Sin embargo. De hecho tam- . void f( int x. Cuando mas de una funcion comparte el mismo nombre. Sin embargo C manipulara el nombre de ambas funciones en la Figura 7. Muchas de las reglas basicas para interfazar C y ensamblador tambien son validas para C++. Este hecho explica una regla de sobre carga en C++. con el mismo nombre y firma. 7.

En la terminologúa de C++. C++ extiende la palabra clave extern para permitir especificar que la funciúon o variable global que modifica usa las convenciones de llamado de C. ENSAMBLADOR Y C++ 153 bien manipula los nombres de las variables globales codificando el tipo de variable de manera similar a la firma de las funciones. Esta característica de C++ es conocida como encadenamiento seguro de tipos. Ya que cada compilador usa sus reglas de manipulaciún de nombres. El programa compilaría y encadenaría. 35 Si se empareja entonces crea un CALL a la funcion correcta usando las reglas de manipulaciún del compilador. Si uno desea escribir una funcion en ensamblador que sera usada con codigo C++. entonces la funciún printf serú manipulada y el compilador no producirú un CALL a la etiqueta _printf. use el prototipo: extern ”C” int printf ( const char *. la funciún printf no se puede sobrecargar. Esto es muy importante ya que hay una gran cantidad de coúdigo de C uútil. el cúdigo de C++ compilado con compiladores diferentes no es posible unirlo. Ademúas permitirle a uno llamar cúdigo de C heredado. El estudiante astuto puede preguntar si el coúdigo de la Figura 7. En C++. El prototipo es: int printf ( const char DJGPP podría manipular esto como _printf__FPCce (la F es por función. esto producirá un error de encadenado. se producirá un error del encadenador. .7. Tambien se expone a otro tipo de error. haciendo esto. Por ejemplo para declarar que printf tenga el encadenamiento de C.bajarú como se espera. esto podría pasar. En C esto puede ser un problema muy difícil de depurar. Las reglas para este proceso están más allá del alcance de este libro. Este hecho es importante cuando se considera usar una biblioteca de C++ precompilada. Así uno puede definir una variable global en un archivo como de cierto tipo y entonces intentar usarla en otro archivo como de un tipo erroneo.10 tra. Esto suministra una manera facil de interfazar C++ y ensamblador . el busca una funcion donde emparejen los tipos de los argumentos pasados a la funcion. si el prototipo para printf fue colocado simplemente al principio del archivo. ). Esto le dice al compilador que no use las reglas de manipulaciúon de nombres en esta funciúon. pero en su lugar usar las reglas de C. Esto podría no llamar la funciún printf de la biblioteca de C. Cuando el compilador de C++ esta examinando la sintaxis un llamado a funcion. prototipos inconsistentes.2. Claro estú. C++ tambien le permite a uno llamar codigo de ensamblador usando las convenciones normales de llamado. Esto es un valido. definir la funcion para que use el encadenamiento de C y la convenciúon de llamado de C. el compilador considerara las conversiones de tipos de los argumentos. C por constante. Esto ocurre cuando la definicion de una funciín en un modulo no concuerda con el prototipo usado por otro mídulo. C no atrapa este error.. 35El emparejamiento no tiene que ser exacto. .. c por char y e por elipsis). P por apuntador. Ya que C++ manipula los nombres de todas las funciones. Consulte un libro de C++ para los detalles. debe haber alguna forma para el cúdigo de C++ llamar codigo de C. pero tendraí un comportamiento indefinido cuando se invoca el codigo colocando diferentes tipos de datos en la pila de los que la funciín espera. las variables y funciones globales usan el encadenamiento de C. Sin embargo. debe conocer las reglas de manipulacion de nombres del compilador (o usar la tecnica explicada abajo).

ella . Por ejemplo. considere el codigo de la Figura 7. ellos realmente son tan solo apuntadores. El bloque se enmarca por las típicas llaves: extern ”C” { /* encadenamiento tipo C a variables globales y prototipos de funciones */ } void f( int & x ) // el & denota una referencia { x++. } int main() { int y = 5. Los compiladores de C++ definen el macro __cplusplus (con dos guiones bajos adelante).154 CAPÍTULO 7. Referencias Las referencias son otra característica nueva C++. El compilador solo le oculta esto al programador (tal como los compiladores de Pascal implementan los parámetros var como apuntadores). } Figura 7.11. La porcion de cídigo anterior encierra todo el archivo de cabecera en un bloque extern C si el archivo de cabecera es compilado como C++. pasa la dirección de y. Actualmente los parámetros por referencia son muy simples. obeserve que //no hay & aac printf (” %d\n”. en ellos se encontra al principio de cada archivo de cabecera: #ifdef —cplusplus extern ”C” { #endif Y una construccion parecida cerca del final conteniendo el fin de la llave. pero no hace nada si es compilado como C (ya que el compilador de C daría un error de sintaxis para extern C).2. Ellas le permiten a uno pasar parámetros a funciones sin usar apuntadores explícitamente. Esta misma tecnica puede ser usada por cualquier programador para crear un archivo de cabecera para las rutinas de ensamblador que pueden ser usadas con C o C++. Si uno escribií la funciín f en ensamblador.11: Ejemplo de referencia Si uno examina los archivos de encabezado de ANSI C que vienen con los compiladores de C/C++ de hoy día. 7. y). // imprime 6! return 0.2. ESTRUCTURAS Y C++ Por conveniencia C++ tambien permite el encadenamiento de un bloque de funciones y variables globales a ser definidas. Cuando el compilador genera el ensamblador para el llamado a la funcion en la línea 7. f(y). //se pasa la referencia a y.

Sin embargo. Funciones inline Las funciones inline son otra característica de C++37. el tiempo que se toma en llamar la funciín puede ser mayor que el tiempo necesario en realizar las operaciones en la funcion. aín esta version no darí la respuesta correcta para SQR(X++). pero que no hace el bloque de codigo para CALL. esta expresion podría ser reescrita en la notaciín de funcion como operator +(a. pero esto requeriría escribir en la sintaxis del operador como &a + &b.2. los macros basados en el procesador que toman parámetros que son muy propensos a tener errores.2. 36 Claro esta ella podría esperar declarar la funcion con encadenamiento C para evitar la manipulación de nombres discutida en la sección 7. que escribir un macro que eleva al cuadrado un nímero podría verse como: #define SQR(x) ((x)*(x)) Porque el preprocesador no entiende C hace simples sustituciones. Por eficiencia uno podría querer pasar la direcciín de los objetos cadena en lugar de pasarlos por valor. Así. si a y b fueran cadenas. Por ejemplo. se requieren los paréntesis para calcular la respuesta correcta en la mayoría de los casos. En lugar de ello los llamados a funciones inline son reemplazados por el cídigo que realiza la funcion. C++ le permite a una funcion ser inline colocando la palabra inline clave al frente de la definiciín de la funciín. La funcion inline es una manera mucho mís amigable de escribir cídigo que se vea como una funcion normal. Las referencias son solo una conveniencia que es especialmente ítil para la sobrecarga de operadores. un uso comín es definir el operador mas (+) para unir objetos tipo cadena.7. Los macros se usan porque ellos eliminan el trabajo extra de hacer un llamad a funcion para una funcion elemental. usando referencias. Las funciones inline son hechas para reemplazar. Esta es otra característica de C++ que le permite a uno definir el significado para los operadores en tipos de estructuras o clases. 7.3. Como se demostro en el capítulo de subprogramas realizar un llamado a funciín involucra varios pasos. a + b debería retornar la union de las cadenas a y b.&b). Recuerde de C. C++ actualmente llama una funcion para hacer esto (de hecho.b)). Por cada funcion elemental. ENSAMBLADOR Y C++ 155 se debería comportar como si el prototipo fuera36: void f( int * xp). Sin embargo. Esto sería muy complicado y confuso.2. Sin referencias esto se podría hacer como operator +(&a.1 37Los compiladores de C a menudo soportan esta característica como una extension de ANSI C . uno puede escribir a + b que se ve muy natural.

return 0. ESTRUCTURAS Y C++ inline int inline_f ( int x ) { return x*x. No se colocan los parámetros en la pila. no se hace el salto. eax Sin embargo. [ebp-8] eax. asumiendo que x estí en la direcciín ebp-8 y y estí en ebp-4: 1 2 3 4 push call pop mov dword [ebp-8] _f ecx [ebp-4]. y = inline_f (x). Este íltimo punto es verdad para este ejemplo pero no siempre es cierto. } int f( int x ) { return x*x.156 CAPÍTULO 7. eax En este caso hay dos ventajas de las funciones inline. . el llamado a la funciín inline_f de la línea 11 podría verse como: 1 2 3 mov imul mov eax. Primero la funcioín en línea es rápida. } int main() { } int y.12. eax [ebp-4]. Segundo el llamado de las funciones en línea usa menos cídigo. Figura 7. x = 5. El llamado a la funcion f en la línea 10 hace un llamado normal a una funcion (en ensamblador. y = f(x). La principal desventaja de las funciones inline es que el codigo en línea no se encadena y así el codigo de una funcion en línea debe estar disponible en todos los archivos que la usen. no se crea el macro de la pila ni se destruye. El ejemplo anterior prueba esto.12: ejemplo inline Por ejemplo considere las funciones declaradas en la Figura 7. El llamado de una funcion normal solo requiere el conocimiento de los parámetros.

Clases // Construcctor por defecto // destructor // funciones miembro // datos miembro x) Una clase en C++ describe un tipo de objeto. }. Recuerde que para las funciones no inline. ENSAMBLADOR Y C++ 157 class Simple { public : Simple (). } Figura 7. Un objeto tiene como miembros a datos y funciones38. la convención de llamado y el nombre de la etiqueta para la función.13: Una clase simple de C++ el tipo de valor de retorno. Por todas estas razones. . void set_data ( int ). 7. Esto significa que si cualquier parte de una funcion en línea se cambia. } Simple::~Simple() { /* null body */ } int Simple:: get_data () const { return data . a menudo los archivos que usan la función no necesitan ser recompilados. Simple::Simple() { data = 0. int get_data () const . } void Simple:: set_data ( int { data = x.2. el codigo de las funciones en línea se colocan normalmente en los archivos de cabecera. todos los archivos fuentes que usen la función deben ser recompilados.4. ~Simple(). es una estructura con 38a menudo llamados en C++ funciones miembro o mas generalmente métodos. Toda esta informacion está disponible en el prototipo de la funcion. En otras palabras. Sin embargo al usar funciones inline se requiere conocer todo el codigo de la funcion.7.2. si el prototipo cambia. Esta próactica es contraria a la regla normal dura y rapida de C que las instrucciones de código ejecutable nunca se colocan en los archivos de cabecera. private : int data.

C++usa la palabra normal en C con un solo int como miembro. 39 (Los compiladores de Borland y Microsoft El sistema de compilador gcc incluye su propio ensamblador llamado gas. int x ) { object—>data = x. Considere la clase definida en la Figura 7. Ellas son pasadas como un está dentro de la función.14.s y desafortunadamente usa la sintaxis del lenguaje ensamblador AT&T que es diferente de la sintaxis de NASM y MASM. [ebp + 1 2 ] .13. parametro es un apuntador al objeto al cual la función pertenece.13.html). esp . ESTRUCTURAS Y C++ void set_data ( Simple * object. Por ejemplo considere el metodo set_data de la clase simple de la Figura 7. parámetro oculto. [ebp + 8 ] . Una variable de tipo Simple se podría ver tal como una estructura Ahora.al cenadas en la memoria asignada a la estructura. Este miembro.14: Version de C de Simple::set_data() 1 2 3 4 4 5 6 8 7 io _set_data ___ 6 Simplei: push ebp mov ebp. data tiene un desplazamiento de 0 leave ret Figura 7. Sin embargo las funciones apunta.multimania. edx = parametro entero mov [eax].d°r de un objeto que miembro son diferentes de las otras funciones . edx . Si se escribio en C se vería como una funcion que fue pasada explícitamente un apuntador al objeto.158 CAPÍTULO 7. que convierte del formato AT&T al for9 mato NASM .com/placr/a2i. Hay tambien un programa mínimo llamado a2i (http://www. } Figura 7. Las funciones no son alma.15: Salida del compilador para Simple::set_data( int ) datos y funciones asociadas a ella. nombre manipulador mov eax. Para DJGPP y gcc el archivo ensamblador finaliza con una extension o .clave this para dcceder. El ensamblador gas usa la sintaxis AT&T y así el compilador produce el código en el formato de gas. eax = apuntador al objeto (this) mov edx. Como muestra la Figura 7. Hay varias paginas web que discuten las diferencias entre los formatos INTEL y AT&T. La opcion -S en el compilador DJGPP (y en los compiladores gcc y Borland tambien) le dice al compilador producir un archivo ensamblador conteniendo el lenguaje ensamblador equivalente para el cádigo producido.

se almacena con un desplazamiento cero en la estructura Simple. La clase tiene tres constructores: El primero (línea 9) inicia la instancia de la clase usando un entero sin signo normal. que es el ínico dato en la clase.7. 40Como n¿Por . 41 (la palabra doble menos significativa es la palabra doble que esta en la posiciín 0). 42. aparece el prologo típico de la funciín.16 muestra la definicion de la clase Big_int. cada compilador codificara esta informaciín diferente en la etiqueta manipulada. La línea 6 almacena el parámetro x en EDX y la línea 7 almacena EDX en la palabra doble a la que apunta EAX.15 muestra la salida de DJGPP convertida a la sintaxis de MASM con comentarios añadidos para clarificar el proposito de las instrucciones. sería almacenado en un arreglo de enteros sin signo (palabras dobles). El dato miembro size_ de la clase se le asigna un desplazamiento de cero y el miembro number_ se le asigna un desplazamiento de 4. El tamaño de un Big_int se mide por el tamaño del arreglo sin signo que es usado para almacenar este dato. tal como antes. Luego en las líneas 2 y 3. La Figura 7. El es normal nada esta oculto en el codigo ensamblador que? Porque las operaciones de adición siempre se comenzaran a procesar al inicio del arreglo y se moveró hacia adelante. solo instancias objeto con el mismo tamaño se pueden sumar o restar la una de la otra. El texto solo se referira a alguna parte del codigo.asm usando la sintaxis MASM). Este no es el parámetro X. La Figura 7. Los parámetros se codifican tal que la clase pueda sobre cargar el metodo set_data para tomar otros parámetros como es normal en las funciones C++. el segundo (línea 18) inicia la instancia usando una cadena que contiene un valor en hexadecimal. Las palabras dobles se almacenan en orden inverso. es el parámetro oculto. Ejemplo Esta secciín usa las ideas del capítulo para crear una clase de C++ que representa un entero sin signo de tamaño arbitrario. ENSAMBLADOR Y C++ 159 generan un archivo con extension . el nombre de la clase y los paríametros. En la primera línea. Se puede hacer de cualquier tamaño usando memoria dinamica. observe que el metodo set_data se le asigna la etiqueta manipulada que codifica el nombre del metodo. Para simplificar este ejemplo. Esto es el miembro data del objeto Simple sobre el cual actía. 42Vea el código fuente example para el código completo de este ejemplo. Ya que el entero puede ser de cualquier tamanño. 40 que apunta al objeto sobre el cual actía. Sin embargo. En la línea 5 se almacena el primer parámetro de la pila en EAX. El nombre de la clase se codifica porque otras clases podrían tener un metodo llamado set_data y los dos metodos deben tener etiqueta diferente.2.

ESTRUCTURAS Y C++ class Big_int { public: /* * áParmetros: * size * * * */ explicit Big_int ( size_t size . friend BigJnt operator — ( const BigJnt & opl. const BigJnt & op2 ). friend bool operator < ( const BigJnt & opl. friend ostream & operator << ( ostream & os. const BigJnt & op2). valor Figura 7. Big_int ( size_t const char * initiaLvalue ). al arreglo sin singno que almacena el unsigned * number. }. friend BigJnt operator + ( const BigJnt & opl. private: ntamao del arreglo sin signo apuntador size_t size_ . const BigJnt & op2 ). friend bool operator == ( const BigJnt & opl. const BigJnt & operator = ( const BigJnt & bigJnt_to_copy ). size .. // retorna el ntamao de BigJnt (en etrminos de unsigned int) size_t size () const. ~ BigJnt ().16: Definición de la clase BigJnt . BigJnt ( const BigJnt & bigJnt_to_copy ). const BigJnt & op ). unsigned initiaLvalue = 0).160 CAPÍTULO 7. const BigJnt & op2). * áParmetros size initiaLvalue — ntamao del entero expresado como el únmero de unsigned int normales initiaLvalue — valor inicial de BigJnt como un unsigned int normal — ntamao del entero expresado como el ánmero de unsigned int normales — valor inicial de BigJnt como la cadena que almacena la á representacin hexadecimal del valor.

const Big_int & op2) { Big_int result (opl. opl. int sub_big_ints ( Big_int & res. if ( res == 2) throw Big_int:: Size_mismatch(). opl. if ( res == l) throw Big_int:: Overflow(). return result .2. } Figura 7. } inline Big_int operator + ( const Big_int & opl. ENSAMBLADOR Y C++ 161 // Prototipos para las rutinas en ensamblador extern ”C” { int add_big_ints ( Big_int & res. const Big_int & op2). op2). return result . const Big_int & op2).17: Código de la aritmética de la clase Big_int .7. } inline Big_int operator — ( const Big_int & opl. if ( res == 2) throw Big_int:: Size_mismatch(). const Big_int & op2) { Big_int result (opl. int res = sub_big_ints (result . op2). size ()). int res = add_big_ints (result . const Big_int & opl. const Big_int & opl. size ()). if (res == l) throw Big_int:: Overflow().

esp ebx esi 0 .asm): segment %define size_offset %define number_offset 4 %define EXIT_OK 0 %define EXIT_OVERFLOW 1 %define EXIT_SIZE_MISMATCH 2 . La Figura 7. Esta discusiín se enfoca en como trabajan los operadores de la suma y de la resta ya que es allí donde se usa el lenguaje ensamblador.162 CAPÍTULO 7. Ellas muestran como los operadores se usab para llamar las rutinas en ensamblador. solo se discutirá ací la rutina de ensamblador add_big_ints. las funciones en línea de los operadores se usan para establecer el encadenamiento de rutinas en ensamblador. Por brevedad. Parámetros para las rutinas add y sub %define res ebp+ 8 %define op1 ebp+ 1 2 %define op2 ebp+16 add_big_ints: push mov push push ebp ebp. ¿Por que se usa el ensamblador aca despues de todo? Recuerde que realiza aritmetica de precision míltiple. Esta tecnica tambien elimina la necesidad de lanzar una excepcion desde ensamblador. el carry se debe mover de una palabra doble para sumar la siguiente palabra doble. Sigue el codigo de esta rutina (de big_math.17 muestra las partes relevantes del archivo de encabezado para estos operadores. Realizar la suma podría solo hacerlo recalculando la bandera de carry y condicionalmente sumar esto a la siguiente palabra doble. Ya que cada compilador usa sus reglas de manipulacion de nombres radicalmente diferentes para las funciones de los operadores. C++ (y C) no le permiten al programador acceder a la bandera de carry de la CPU. Es mucho mías eficiente escribir el codigo en ensamblador donde se puede acceder a la bandera de carry y usando la instruccion ADC que automaticamente suma la bandera de carry. Esto hace relativamente fácil portar a diferentes compiladores y es tan rápido como los llamados directos. ESTRUCTURAS Y C++ tercer constructor (línea 21) es el constructor copia.

7.2. ENSAMBLADOR Y C++ 163 .

borra bandera de carry edx. ebx. ecx = size of Big_int’s respectivos arreglos mov mov mov clc xor . esi. valor de retorno = EXIT . eax edx add_ loop overflow eax. edx . [opl] [op2 ] [res] tienen el mismo tamaño primero mov mov mov . [esi+4*edx] [ebx + 4*edx]. edi. eax. edi. eax .size_ != op2 .size_ != res.ñumber_ res. op1 . ESTRUCTURAS Y C++ push edi esi señala a op1 edi señala a op2 ebx señala a res esi. op1 .ñumber_ op2 . Asegurarse que todos los 3 Big_Int mov cmp jne cmp jne mov .size_ . eax registrosseñalan a sus op1 .size_ . addition loop add_loop mov adc mov inc loop jc ok done: xor eax. [esi + size_offset] [edi + size_offset] sizes_not_equal eax. los esi = edi = ebx = ebx. no altera la bandera de .164 CAPÍTULO 7. edx = 0 .number_ [ebx + ñumber_offset] [esi + ñumber_offset] [edi + ñumber_offset] . [edi+4*edx] eax. ahora eax. . [ebx + size_offset] sizes_not_equal ecx.

ctor de conversion que convierta un unsigned int en un Big_int. La Figura 7. el bucle comienza en el inicio del arreglo y se mueve adelante hasta el final. Las líneas 44 a 46 ajustan los registros para apuntar al arreglo usado por los objetos respectivos en lugar de los objetos en sí mismos.7. La línea 59mexamina el desborde.2.asm done: pop pop pop leave ret Con la esperanza que la mayoría del código fuera claro al lector por ahora.18 muestra un corto ejemplo del uso de la clase Big_int. Las líneas 25 a 27 almacenan los apuntadores a los objetos Big_int pasados a la funcion en registro.1. Primero. (Una vez mas. Ya que las palabras dobles en el arreglo se almacenan en el orden de little endian. si hay desborde la bandera de carry se debería fijar con la ultima suma de la palabra doble mís significativa. Una implementacion mas sofisticada de la clase debería permitir sumar objetos de cualquier tamanño.5. EXIT_SIZE_MISMATCH edi esi ebx big_math. 7. EXIT_OVERFLOW eax. (sin embargo exhorta al lector a hacer esto). el desplazamiento de number_ se añade al apuntador del objeto). Esto es necesario por dos razones. no hay un construc. esto hace la conversiín problemítica ya que podría ser difícil conocer que tamaño convertir a. Recuerde que las referencias realmente son solo apuntadores. . Las líneas 31 a 35 aseguran que los tamaños de los 3 objetos de los arreglos sean iguales (Observe que el desplazamiento de size_ se añade al apuntador para acceder al dato miembro). El bucle en las líneas 52 a 57 suma los enteros almacenados en los arreglos sumando primero la palabra doble menos significativa. El autor no desea complicar este ejemplo implementandolo aca. luego la siguiente palabra doble y así sucesivamente. Herencia y polimorfismo La herencia le permite a una clase heredar los datos y metodos de otra. La suma se realiza en esta secuencia para la aritmetica de precisión extendida (vea la Sección 2. Segundo. ENSA MBLA JmP DOR Y overflow: C++ mov jmp done sizes_not_equal: mov done eax. Observe que las constantes Big_int se deben declarar explícitamente como en la línea 16.5).2. solo Big_Int del mismo tamaño se pueden sumar.

int main() { try { Big_int b(5. Big_int d (5.1) << endl. ESTRUCTURAS Y C++ #include ”big_int. i < 2.hpp” #include <iostream> using namespace std. cout << ”c = ” << c << endl. cout << ”d = ” << d << endl.18: Uso simple de Big_int . return 0. i++ ) { c = c + a. Big_int a(5.”80000000000010230”). for ( int i=0. cout << a << ” + ” << b << ” = ” << c << endl. Figura 7. cout << ”c == d ” << (c == d) << endl. } cout << ”c—1 = ” << c — Big_int(5. } } } } catch( Big_int :: Overflow ) { cerr << ’’Overflow” << endl. cout << ”c > d ” << (c > d) << endl.166 CAPÍTULO 7. ”8000000000000a00b”). Big_int c = a + b. ”12345678”). } catch( const char * str ) { cerr << ’’Caught: ” << str << endl. catch( Big_int :: Size_mismatch ) { cerr << ”Size mismatch” << endl.

B b.7. } m()” << endl. } Figura 7.bd) << endl. f(&a). void f( A * p ) { p—>ad = 5. } int main() { A a. cout << ”ñTamao de a: ” << sizeof(a) << ” Desplazamiento de ad ” << offsetof(A. p-em(). }.ad) << ” Desplazamiento de bd ” << offsetof(B. return 0.ad) << endl.2.19: Herencia simple m()” << endl. }. class B : public A { public: void __cdecl m() { cout << ”B:: int bd. ENSAMBLADOR Y C++ 167 #include <cstddef> #include <iostream> using namespace std. class A { public: void __cdecl m() { cout << ”A:: int ad. cout << ”ñTamao de b: ” << sizeof(b) << ” Desplazamiento de ad ” << offsetof(B. f(&b). } .

nombre del mótodo manipulado para :m() A::m() Figura 7. pasar la dirección del objeto a A . } l int bd. nombre de la función manipulador f _ FP1A: push mov mov mov mov push call add leave ret ebp ebp. Observe que en la salida del metodo m de A fue llamado por los objetos . 4 . [ebp+8 ] dword [eax]. ESTRUCTURAS Y C++ . usar desplazamiento 0 para ad . }. a un objeto A de cualquier tipo derivado de A.21: Herencia polimorfica Por ejemplo consider el código de la Figura 7. eax apunta al objeto . } l int ad. donde la clase B hereda de A. esp eax. La Figura 7. Esto es importante ya que la función f puede ser pasada como un apuntador. [ebp+8 ] eax _m _ 1A esp. class B : public A { public : virtual void __cdec m() { cout << ”B::m()” << endl.20 muestra el código asm (editado) para la funcion (generada por gcc). 5 eax. La salida del programa es: Tamaño de a: 4 Desplazamiento de ad: 0 Tamaño de b: 8 Desplazamiento de ad: 0 Desplazamiento de bd: 4 A::m() A::m() Observe que los miembros ad de ambas clases (B hereda de A) estón en el mismo desplazamiento.20: Código en ensamblador para la herencia simple class A { public : virtual void __cdec m() { cout << ”A::m()” << endl.168 CAPÍTULO 7. Figura 7.19. }. Ella muestra dos clases A y B.

Microsoft y Borland. la salida del programa cambia: Tamaño de a: 8 Desplazamiento de ad: 4 Tamaño de b: 12 Desplazamiento de ad: 4 Desplazamiento de bd: 8 A::m() B::m() Ahora la segunda llamada a f llama el metodo B::m() porque es pasado a un objeto B. 5 ecx. Tambien el desplazamiento de ad es 4 no es 0. Interesado en simplificar esta discusion. Desde ensamblador. Desafortunadamente la implementacion de gcc estó en transición al momento de escribir esto y es significativamente mas complicada que la implementacion inicial. [ecx] eax.go necesita ser cambiado. 4 ebp p->ad = 5. Con estos cambios. el autor so lo cubriróa la implementacióon del polimorfismo que usan los compiladores de Windows. Esta implementacion no ha cambiado en muchos años y probablemente no cambie en el futuro cercano. el metodo llamado dependería de que tipo de objeto se pasa a la funcion. C++ tiene esta característica apagada por defecto. Para programación realmente orientada a objetos.22: Código en ensamblador para la función f() a y b. [ebp + 8 ] eax dword [edx] esp. El polimorfismo puede implementarse de muchas maneras. La Figura 7. [ebp + 8 ] edx. ENSAMBLADOR Y C++ ?f@@YAXPAVA@@@Z: push ebp mov mov mov mov mov mov push call add pop ret ebp. ecx = p edx = apuntador a vtable eax = p empuja apuntador "this" llama primera función en vtable limpia la pila 169 Figura 7.21 muestra como las dos clases serían cambiadas.2. El tamaño de A es ahora 8 (y B es 12). Uno usa la palabra clave virtual para i habilitarla.7. uno puede ver que la llamada a A::m() esta empotrado en la funcion. Esto es conocido como polimorfismo. [ebp+8 ] dword [eax+4]. ¿Que es un . Esto no es lo ónico que cambia. esp eax. Nada del otro codi.

} m2() { __cdecl cout << ”A::m2()” << endl.170 CAPÍTULO 7. m1func_pointer(pa). cout << hex << ’’óDireccin de la vtable = ” << vt << endl. for ( int i=0. << ”a: ” cout << ”b1: cout return 0. print_vtable (& a ). Figura 7. i++ ) cout << ”dword ” << i << ”: ” << vt[i] << endl. } ” << endl. public A { // B hereda de A m2() /* vtable _cdecl m1() { cout << ”B::m1()” << endl. // vt ve la vtable como un arreglo de apuntadores void ** vt = reinterpret_cast<void **>(p[0]). prin^vtable (& b). print_vtable (&b2).23: Un ejemplo más complicado . } objeto dado */ print_vtable { imprime la de un void (A * pa ) // p ve a pa como un arreglo de palabras dobles unsigned * p = re¡nterpret_cast<uns¡gned *>(pa). m2func_pointer(pa). }. // llamado a funciones virtuales de una manera // EXTREMADAMENTE no portable void (*m1func_pointer)(A *). i < 2. // function pointer variable m2func_pointer = reinterpret_cast<void (*)(A*)>(vt[1]). public: virtual void __cdecl m1() { cout << ”A::m1()” << endl. // function pointer variable m1func_pointer = reinterpret_cast<void (*)(A*)>(vt[0]). cout B b2. ESTRUCTURAS Y C++ int ad. ” << endl. } virtual void class B public: virtual void int bd. << ”b2: << endl. // Llama al emtodo m1 a etravs de un // apuntador a una ofuncin void (*m2func_pointer)(A *). B b1. // llamado al emtodo m2 a etravs de / / u n apuntador a o funcin } int main() { A a. class A { }.

Esto le permite al codigo llamar el metodo apropiado para el objeto. El codigo no es muy eficiente porque se genera sin tener activadas las optimizaciones del compilador. en el tope del árbol de herencia.21 estan declarados explícitamente para usar la convecion de llamado de usando la plabra clave __cdecl.7. Una clase C++ que tiene cualquier metodo virtual se le da un campo oculto extra que es un apuntador a un arreglo de apuntadores a metodos. 43.20) fija una llamada a cierto metodo es llamado primer vínculo ya que aquá el metodo esta atado en el tiempo de compilaciáon. este valor ya está en el registro ECX el fue colocado en la línea 8 y la línea 10 podía ser quitado y la próxima línea cambiada para empujar ECX. El caso normal (Figura 7. La pila se sigue usando para los otros paráametros explácitos del máetodo.2. El pasa un apuntador al objeto sobre el que actáa el metodo en el registro ECX en lugar de usar la pila. Este tipo de llamadas es un ejemplo de vínculo tardío . 44. ENSAMBLADOR Y C++ 0 vtablep ad 4 bd 8 b1 4 < &B::m1() 171 0 vtable Figura 7. Para las clases A y B este apuntador se almacena con un desplazamiento 0. La línea 12 llama al metodo virtual ramificándose en la primera direccion en la vtable. uno puede ver que la llamada al metodo m no es a una etiqueta. Esta llamada no usa una etiqueta. La direccián del objeto se empuja en la pila en la línea 11. 44Claro esta.19) para el metodo virtual del programa. Los compiladores de Windows siempre colocan este apuntador al inicio de la clase. Esta tabla se llama a menudo vtable. Mirando en el codigo ensamblador (Figura 7. El Para las clases sin métodos virtuales los compiladores de C++ siempre hacen la clase compatible con una estructura normal de C con los mismos miembros. La línea 9 encuentra la direccián de la vtable de objeto.24: Representación interna de b1 desplazamiento de cero? La respuesta a esta pregunta está relacionada con la implementacion del polimorfismo. El vínculo tardáo retraza la decision de que metodo llamar hasta que el codigo se este ejecutando. s . El lector atento se sorprenderáa por quáelos metodos de la clase en la Figura 7. salta a la direccián de codigo apuntada por EDX.22) generado para la funcion f (de la Figura 7. Por defecto Microsoft usa una conveciáon de llamado diferente a la convenciáon normal de C para los metodos de las clases de C++.

Ahora miraremos un ejemplo un poco mas complicado (Figura 7. La dirección del metodo se almacena en un apuntador a funcion de tipo C con un apuntador explócito this. Un hecho importante es que uno tendría que tener mucho cuidado cuando lee y escribe variables de una clase a un archivo binario. Es solo para ilustrar como los metodos virtuales usan la vtable. Uno no puede sío usar una lectura o escritura binaria en un objeto completo ya que esto podría leer o escribir la apuntador vtable al archivo.24 muestra como un objeto b se ve en memoria. Primero observe la direccióon de vtable para cada objeto.23 modificador __cdecl le dice usar la convención de llamado estóndar de C. Los apuntadores al metodo m2 son los mismos vtables de las clases A y B porque la clase B hereda el móetodo m2 de la clase A. .25. Ahora mire en las direcciones en las vtables.25 muestra la salida del programa. Las lóneas 25 a 32 muestran como uno podría llamar a una funcion virtual leyendo su direccion de la vtable para el objeto.23). La Figura 7. ESTRUCTURAS Y C++ a: vtable address = 004120E8 dword 0: 00401320 dword 1: 00401350 A::m1() A::m2() b1 : vtable address = 004120F0 dword 0: 004013A0 dword 1: 00401350 B::m1() A::m2() b2 : vtable address = 004120F0 dword 0: 004013A0 dword 1: 00401350 B::m1() A::m2() Figura 7. Sin embargo. En el. uno puede ver que trabaja. 45. Borland C++ usa la convención de llamado de C por defecto. por favor no escriba codigo como este. ellas compartan la misma vtable.Recuerde que ya que la clase B no define su propio móetodo m2.172 CAPÍTULO 7. Una vtable es una propiedad de la clase no un objeto (como un miembro static).25: Salida del programa de la Figura 7. hereda el móetodo de la clase A. La Figura 7. Las dos direcciones de los objetos B son iguales y asó. las clases A y B cada una tiene dos metodos m1 y m2. Mirando la salida en ensamblador uno puede determinar que el apuntador al móetodo m1 es un desplazamiento cero (o palabra doble 0 ) y m2 es un desplazamiento de 4 (palabra doble 1). Este es un apuntador a donde la vtable reside en la memoria 45Recuerde que este código solo trabaja con los compiladores de Borland y MS. no para gcc. Hay algunas lecciones prácticas para aprender de esto. De la salida en la Figura 7.

puede ignorar la vtabley llamar el metodo directamente.2. El codigo para el metodo virtual parece exíctamente como un metodo no virtual. Sílo los compiladores que implementan metodos virtuales como Microsoft pueden crear clases COM. (usando el primer vínculo). ENSAMBLADOR Y C++ 173 del programa y variará de programa a programa. Esta es la razín por la cual Borland usa la misma implementacion que Microsoft y una de las principales razones por las cuales gcc no se puede usar para crear clases COM.7. Otra vez. No hay apuntadores obvios definidos en las clases A o B. es importante tener en cuenta que cada compilador implementa los metodos virtuales a su manera. los objetos de las clases COM (Component Object Model) usan las vtables para implementar interfaces COM46. Solo el codigo que llama es diferente. Este mismo problema puede ocurrir con estructuras. pero en C. manejo de excepciones y herencia míltiple) esrán mas alla del alcance de este texto. 7. las estructuras solo tienen apuntadores en ellas si el programador las coloca explícitamente en ellas. Si el compilador puede estar absolutamente seguro de que metodo virtual sera llamado. . En Windows. 46Las clases COM también usan la convención de llamado code __stdcall no la normal de C. un buen punto de partida es The Annotated C++ Reference Manual por Ellis and Stroustrup y The Design and Evolution of C++ por Stroustrup. Otras características de C+—+ Las otras características de C++ (informacion en tiempo de ejecucion. Si el lector desea avanzar mas.6.2.

Muchas de las instrucciones con dos operandos permiten los mismos operandos. Instrucciones para enteros Esta seccióon lista y describe las acciones y formatos de las instrucciones para enteros de la familia de Intel 80x86 Los formatos usan las siguientes abreviaturas: R R8 R16 R32 SR M M8 M16 M32 I registro general registro de 8 bits registro de 16 bits registro de 32 bits registro de segmento memoria byte palabra palabra doble valor inmediato Estas se pueden combinar para las instrucciones con varios operandos.R R.Apéndice A Instrucciones del 80x86 A. Finalmente. Si se puede usar como operando un registro de 8 bits o la memoria. si el bit es modificado de alguna manera no definida se coloca un ? en la columna.I. no se listan bajo la columna de FLAGS. se usa la abreviacion R/M8 La tabla tambióen muestra cóomo se afectan varias de las banderas del registro FLAGS.R significa que la instruccion toma dos operandos de registro. se muestra un 1 o un 0 en la columna.1. 174 . Si la columna esta en blanco. Si el bit cambia a un valor que depende del operando de la instrucción se coloca un C en la columna. el bit correspondiente no se afecta. Si el bit siempre cambia a algón valore en particular. Ya que las únicas instrucciones que cambian las banderas de dirección son CLD y STD. Por ejemplo el formato R.M R. La abreviacion 02 se usa para representar estos operandos: R.I M.R M.

R/M32 R16.I RM I I C ? C ? C ? C ? C ? ? ? C ? ? ? ? ? ? ? ? ? C INC INT JA Incremento entero Genera una interrup. 0 Hace el marco de la pila Division con signo Multiplicación con signo RM RM R16.I R32.l.R/M16.I R16.I R32. INSTRUCCIONES PARA ENTEROS 175 Banderas Nombre ADC ADD AND BSWAP CALL CBW CDQ CLC CLD CMC CMP CMPSB CMPSW CMPSD CWD CWDE DEC DIV ENTER IDIV IMUL Descripción Suma con carry Suma enteros AND entre bits Byte Swap Llamado a rutina Convierta byte a palabra Convierte Dword a Qword Borra el Carry borra la bandera de dirección Complementa el carry Compara enteros Compara Bytes Compara Words Compara Dwords O2 C C C C C C C C C C C C C C C C C C C C 0 Formatos O2 O2 O2 R32 RMI O C C 0 S C C C Z C C C A C C ? P C C C C C C 0 C C C C C Convierte Word a Dword en DX:AX Convierte Word a Dword en EAX Decrementa entero RM División sin signo RM I.R/M16 R32.R/M32.cion Salta si esta sobre C C C C C .A.

176 APÉNDICE A. INSTRUCCIONES DEL 80X86 Banderas Nombre JAE JB JBE JC JCXZ JE JG JGE JL JLE JMP JNA JNAE JNB JNBE JNC JNE JNG JNGE JNL JNLE JNO JNS JNZ JO JPE JPO JS JZ Formatos Salta si esta sobre o es I igual Salta si esta bajo I Salta si estí bajo o es igual I Salta si hay carry Salta si CX = 0 Salta si es igual Salta si es mayor Salta si es mayor o igual Salta si es menor Salta si es menor o igual Salto incondicional Salta si no estí sobre I I I I I I I Descripción O S Z A P C RMI I Salta si no esta sobre o es I igual Salta si no estí bajo Salta si no esta bajo o es I igual Salta si no hay carry Salta si no es igual Salta si no es mayor I I I Salta si no es mayor o es I igual Salta si no es menor I Salta si no es menor o I igual Salta si no hay desborde Salta si es positivo Salta si no es cero Salta si hay desborde Salta si hay paridad par I I I I I Salta si hay paridad impar I Salta si hay signo Salta si hay cero I I .

R/M8 R32.R/M8 R32.I R/M.M O S Z A P C Abandona el marco de la pila LODSB Carga byte LODSW Carga palabra LODSD Carga palabra doble LOOP bocle I LOOPE/LOOPZ Bucle si es igual I LOOPNE/LOOPNZ Bucle si no es igual I MOV Mueve datos O2 SR.CL carry C C .R/M16 RM RM RM O2 R/M16 R/M32 MUL NEG NOP NOT OR POP POPA POPF PUSH PUSHA PUSHF RCL Multiplicación sin signo Negación No Opera Complemento a 1 OR entre bits Saca de la pila Saca todo de la pila Saca FLAGS Empuja en la pila Empuja todo en la pila Empuja FLAGS C C ? C ? C ? C ? C C C 0 C C ? C 0 C R/M16 R/M32 I C C C C C Rota a la izquierda con R/M. INSTRUCCIONES PARA ENTEROS 177 Banderas Nombre LAHF LEA LEAVE Descripción Carga FLAGS en AH Carga dirección efectiva Formatos R32.R/M8 R32.R/M16 R/M16.R/M8 R32.SR MOVSB Mueve byte MOVSW Mueve palabra MOVSD Mueve palabra doble MOVSX Mueve con signo R16.l.R/M16 MOVZX Mueve sin signo R16.A.

I R/M.I R/M.I R/M.CL Descripción O C S Z A P C C C C C C C C C C C C C C C C C C C C C C C C C C C C C C C C C C C C .CL carry REP Repite REPE/REPZ Repite si es igual REPNE/REPNZ Repite si no es igual RET Retorno ROL Rota a la izquierda R/M. INSTRUCCIONES PARA ENTEROS 178 Banderas Nombre RCR Formatos Rota a la derecha con R/M.A.l.I R/M.CL ROR SAHF SAL SBB SCASB SCASW SCASD SETA SETAE SETB SETBE SETC SETE SETG SETGE SETL SETLE SETNA SETNAE SETNB SETNBE SETNC SETNE SETNG SETNGE SETNL SETNLE SETNO SETNS Rota a la derecha Copia AH en FLAGS Dezplazamiento a la R/M. CL izquierda Resta con préstamo O2 busca un Byte Busca una palabra Busca una palabra doble fija sobre fija sobre o igual fija bajo fija bajo o igual fija el carry fija igual fija mayor fija mayor o igual fija menor fija menor o igual fija no sobre fija no sobre o igual fija no bajo fija no bajo o igual fija no carry fija no igual fija no mayor fija no mayor o igual fija no menor fijan no menor o igual fija no desborde fija no signo R/M8 R/M8 R/M8 R/M8 R/M8 R/M8 R/M8 R/M8 R/M8 R/M8 R/M8 R/M8 R/M8 R/M8 R/M8 R/M8 R/M8 R/M8 R/M8 R/M8 R/M8 R/M8 R/M.

la derecha CL Desplazamiento loágico a R/M. Para ahorrar espacio.R/M O2 0 C C C 1 di- C 0 C C C C C ? C C C 0 C C ? C 0 A. INSTRUCCIONES DE PUNTO FLOTANTE Banderas Nombre SETNZ SETO SETPE SETPO SETS SETZ SAR SHR SHL STC STD STOSB STOSW STOSD SUB TEST XCHG XOR Descripción fija no cero fija desborde fija paridad par fija paridad impar fija signo fija cero Formatos R/M8 R/M8 R/M8 R/M8 R/M8 R/M8 O S Z A 179 P C Desplazamiento aritmetico R/M.I R/M. Instrucciones de punto flotante En esta sección. La sección de descripción describe brevemente la operacion de la instrucción.R R/M. se describen muchas de las instrucciones del coproce.A. la informacion sobre si la instrucción saca de la pila no se da en la descripcion.I R/M.2. Se usan las siguientes abreviaturas .2.I R/M. La columna de formato muestra que tipo de operandos se pueden suar con cada instruccion.sador matematico del 80x86.I R/M.R R. a la derecha CL Desplazamiento loágico a R/M. la izquierda CL fija el carry fija la bandera de reccián almacena byte almacena palabra almacena palabra doble resta comparaciáon loágica Intercambio XOR entre bits O2 R/M.

ST0] FCHS FCOM src FCOMP src FCOMPP src FCOMI src FCOMIP src FDIV src FDIV dest. ST0 FDIVRP dest [.180 APÉNDICE A. ST0 FADDP dest [. Instrucción FABS FADD src FADD dest. ST0 FDIVP dest [.ST0] FFREE dest FIADD src FICOM src FICOMP src FIDIV src FIDIVR src Descripción ST0 = |ST0| ST0 += src dest += STO dest += ST0 ST0 = —ST0 Compara ST0 y src Compara ST0 y src Compara ST0 y ST1 Formato STn F D STn STn STn F D STn F D STn STn STn F D STn STn STn F D STn STn STn I16 I32 I16 I32 I16 I32 I16 I32 I16 I32 * Compara en FLAGS Compara en FLAGS ST0 /= src dest /= STO dest /= ST0 ST0 = src/ST0 dest = ST0/dest dest = ST0/dest Marca como vacío ST0 += src Compara ST0 y src Compara ST0 y src STO /= src STO = src/ST0 . INSTRUCCIONES DEL 80X86 STn F D E I16 I32 I64 Un registro del coprocesador número de precisión simple en memoria número de precisión doble en memoria número de precision extendida en memoria palabra entera en memoria palabra doble entera en memoria palabra cuadruple entera en memoria Las instrucciones que requieren un Pentium Pro o posterior estóan marcadas con un asterisco (*).ST0] FDIVR src FDIVR dest.

0 Intercambia ST0 y dest STn F D STn F D E I16 I16 AX STn F D STn STn STn F D STn STn STn . INSTRUCCIONES DE PUNTO FLOTANTE Instrucción FILD src FIMUL src FINIT FIST dest FISTP dest FISUB src FISUBR src FLD src FLD1 FLDCW src FLDPI FLDZ FMUL src FMUL dest. 41 binario. 98 definir. 52 archivo de listado.2 ADC. 105 bandera de paridad.0 en la pila Load Control Word Register empuja n en la pila empuja 0. 97 variable local. 96 register. 39 AND. 1 . ST0 FSUBP dest [. 26 Aritmetica de . 108 multidimensionales.A. 35 arrayl.ST0] FSUBR src FSUBR dest.ST0] FRNDINT FSCALE FSQRT FST dest FSTP dest FSTCW dest FSTSW dest FSUB src FSUB dest. 56 ADD. 96 Indice alfabético complemento a dos. 12 tipos de almacenamiento automatic. 13. 25-26 archivo esqueleto. 108 dos dimensiones. 39. 101-105 arreglos.ST0 empuja src en la pila empuja 1. 96 volatile.asm.0 en la pila ST0 *= src dest *= STO dest *= ST0 STn F D STn STn Redondea ST0 ST0 = ST0 x 2 LST1J ST0 = ^STO almacena ST0 almacena ST0 Store Control Word Register Store Status Word Register ST0 -= src dest -= STO dest -= ST0 ST0 = src-ST0 dest = ST0-dest dest = ST0-dest Compare ST0 with 0. ST0 FSUBP dest [. 99-105 defininir estatico.ST0] FTST FXCH dest * 181 Descripción Formato Push src on Stack ST0 *= src I16 I32 I64 I16 I32 I16 I32 I16 I32 I64 I16 I32 I16 I32 STn F D E I16 Inicia el coprocesador almacena ST0 almacena ST0 ST0 -= src ST0 = src . 97-98 multidimensional. ST0 FMULP dest [. 105-107 parámetros.2. 97-118 acceso.

131-132 hardware. 128 cargar y almacenar datos. 111.dos herencia. 8 6 __attribute__. 85 etiquetas . 40 con signo. 127142 carga y almacenamiento de datos. 111. 152 Microsoft. 30-32 aritmetica. 87. véase meto. 40 división. 153-174 clases. 147 pragma pack. 4 C driver. 29 representacióon de complemento a uno. 97 datos. 87 C. 84 retorno de valores. 56 encadenando. 151 Microsoft pragma pack. 85 registros. 24 DJGPP. 39 constructor copia. 24 endianess. 30 magnitud y signo. 33 comparaciones. 128 comparaciones. 147. 112 CMPSD. 172 vtable.182 INDICE ALFABÉTICO estandar. 40 CMPSB. 30 suma. 8 6 llamado . 1 2 enteros. 19 C++. 32 multiplicación. 67. 14 DX. 172-174 CALL. 154 extern C”. 84 parómetros. 112 codigo de operacioín. 166-174 manipulacion de nombres. 98 TIMES. 130131 suma y resta. 1 1 COM. 18-19 direccionamiento indirecto. 35 extensioón del signo. 62 bucle do while. 156-157 vínculo tardío. 65-66 móetodo uno. 62 ensamblador. 155-156 funciones inline.62 invert_endian. 2935 complemento a dos. 129-130 CPU. 87 __stdcall. 164 contando bits. 63-65 metodo tres. 29. 33 CLC. 148. 80. 152 compiler gcc __attribute__. 148. 80 global. 161-166 encadenamiento seguro de tipos. 74 coprocesador de punto flotante. 62-63 convencion de llamado. 6 CWDE. 5-7 80x86. 23. 35-36 precisioón extendida. 157-159 funciones miembro. 45 bucle while. 29-40 bit de signo. 23. 81 DIV. 14 extern. 153156 polimorfismo. 109 CMP. 22. 74 registro. 62-66 móetodo dos. hyperpage60. 151 complemento a dos.26 -------. 127 multiplicación and división. 97 directive global. 172 referencias. 72 stdcall. 14-16 %define. 83 RESX. 23 embalado pragma. 39 representacion. 74. 159-174 ejemplo BigJnt. 15-16 equ. 174 convenciones de llamado. 33 CDQ. 111. 67-68 arreglos. 87 Pascal. 87 stdcall. 24 encadenar. 169-174 primer vínculo. 112 CMPSW. 8 6 compilador. 23 __attibute__. 36-37 extensióon de signo. 174 Compilador Watcom. 36. 2 2 . 85 parametros. 8 6 valores de retorno. 29-32. 2 BSWAP. 50 ejecucióon especulativa. 8687 __cdecl. 71-72 CBW. 33 decimal. 83-87 etiquetas. 39 CLD. 101-105 directiva. 5. 1 depuración. 12 Borland. 24 gcc. 45 byte.

43 JNGE. 9 32-bits. 42 JNP. 128 FLD1. 109 LOOP. 128 FSTSW. 10 modo real. 13 IMUL. 128 FIST. 144 FSQRT. 132 FSUB. 108-118 Interfazando con C. 131 FDIV. 17 print_int. 8-9 . 42 JNS. 44 LOOPZ. 131 FXCH. 131 FIDIV. 3-4 I/O. 32 sin signo. 43 JNL. 43 JG. 17 read_char. 9 memory. 133. 18 183 dump_stack. 130 FLD. 43 JNLE. 131 FICOMP. 43 JL. 44 metodos. 42 JO. 131 FFREE. 109 LODSW. 4-5 púginas. 1 0 segmento. 129 FADDP. 130 FSUBR. 44 LOOPE. 42 JZ. 148 offsetof(). 29. 42 JNZ. 128 FMUL. 11 lenguaje ensamblador. 83 interfazando con C. 105 lenguaje de múquina. 146 etiqueta. 40 estructuras. 17 print_string. 131 FILD. 128 FLDCW. 130 FMULP. 42 JS. 17-19 asm_io library. 130 FSUBRP.asm. 12 math. 159 MASM. 160 GHz. 91 interrupcióon. 44 LOOPNZ. 17 print_nl. 43 JMP. 113-118 modo protegido 16 bits. 42 JE. 43 JNO. 129 FICOM. 109 LODSD. 130 FSCALE. 1 0 JC. 131 FDIVRP. 146-148 campos de bit. 5. 6 hexadecimal. 143 FCOMP. 133 FADD. 37 immediato. 43 JNG. 42 JP. 131 FIDIVR. 132 LEA. 133 FCOM. 131 FDIVP. 4041 JNC. 145-153 alineamiento. 35-36 instrucción if. 128 FISUB. 131 FCOMI. 1 0 16bits. 128 FLDZ. 132. 128 FSTP. 44 LOOPNE. 17 IDIV.asm. 17 read_int. 42 LAHF. 130 FSUBP. 1 0 memoria:segmentos. 151 campos de bits. 18 dump_regs. 128 FSTCW. 129 FCHS. 44 instrucción if . 16 FABS. 18 dump_mem. 132 FCOMIP.ÍNDICE ALFABÉTICO representaciín en complemento a dos. 133 FST. 18 print_char. 128 gas. 145 LODSB. 130 FTST. 37-39 memoria. 45 instrucciones de cadena. 12-13 localidad. 17-19 dump_math. 131 FDIVR. 85-86. 43 JGE. 130 FISUBR. 131 FCOMPP. 43 JLE. 9 virtual. 1 0 segmentos. 128 FIADD. 42 JNE.

119-124 desnormalizada. 111 MOVSW. 80-83 punto flotante. 53 pila. 53 OR. 119-142 aritmetica. 72 variables locales. 111 MOVSD. 52 C. 105 NASM. 33 MUL. 78-80. 58-60 desplazamientos. 58 nibble. 124-126 representacion. 123 . 49 operaciones con bits AND. 12 NEG. 52 desplazamiento lúogicos. 138-142 programas multimúdulo.asm. 53 XOR.184 INDICE ALFABÉTICO MOV. 51 ensamblador.asm. 53 operciones con bits NOT. 85-86 predicción de ramificaciones. 50. 55 prime. 33 MOVZX. 13 MOVSB. 54-55 OR. 49 desplazamientos logicos. 50-51 rotaciones. 70-80 paramúetros. 111 MOVSX. 4 NOT. 35-36. 50 desplazamientos aritmúeticos. 54 Operaciones con bits desplazamientos. 56 prediccioún de ramificaciones. 46-48 prime2.

113 REPNZ. 111. 8 apuntador base. 8 reloj. 113 REPNE. 49 stack parameters. 61 TEST. 68-96 llamado. 91-94 registro. 50 salto condicional. 111. 122 uno oculto. 40 ZF. 51 SAHF. 61 virtual. 136-138 recursion. 8. 112. 112 REPZ. 40 CF. 112 SCSI. 22 segmento de datos. 94 tipos de almacenamiento static. 109 STOSB. 71-80 reentrante. 75 STD. 110-111 REPE. 54 tipos de alamcenamiento global. véase subprograma TASM. 13. 39 SCASB. 12 TCP/IP. 22 segmento de código. 109 EDX:EAX. 123 presicióon simple. 124 quad. 41-43 SAR. 7. 7-8 índice.asm. 8 ESI. 37. 5. 109 SUB. 49 SHR. 51 ROR. 8 EDI. 132 SAL. 109 OF. 51 read. 86 EFLAGS. 109 STOSD. 40 DF. 40 SF. 123 IEEE. 121-124 precisión doble. 74 ROL. véase REPNE. 133-136 RCL. 94 UNICODE. 8 apuntador a la pila. 112 SCASW. 109 registros FLAGS PF. 51 RCR. 41 registrp IP. 71-72. 7. 112 RET. 111.asm. 8. 7. véase REPE. 8 EIP. 8 segmento. 7 32-bit. 122 representacióoon precisioón doble. 57 SHL. 124 precision simple. 112.ÍNDICE ALFABÉTICO 185 desnormalizado. 149-150 segmento bss. 109 FLAGS. 9192 subrutina. 36. 22 segmento text. 6 REP. 39 subprograma. véase segmento code SETxx. 170 . 40 IP. 50 SBB. 56 SETG. 39. 112 SCASD.

186 word. 62 XOR. 8 XCHG. 53 comenzando desde la .

ÍNDICE ALFABÉTIC O izquierda 1No debería sorprenderse de que un número pudiera repetirse en una base. pero no en otra. Piense virtual. 170 .

188 en 1. es periódico en decimal. pero en ternario (base3) sería 0.134. .