You are on page 1of 194

Lenguaje Ensamblador para PC

Paul A. Carter 18 de octubre de 2006

Copyright c 2001, 2002, 2003, 2004 by Paul Carter Traducido al espa nol por Leonardo Rodr guez M ujica. Sus comentaros y sugerencias acerca de la traducci on por favor a: lrodri@udistrital.edu.co Este documento puede ser reproducido y distribuido totalmente (incluida esta paternidad literaria, copyright y aviso de autorizaci on), no se puede cobrar por este documento en s mismo, sin el consentimiento del autor. Esto incluye una utilizaci on racional de extractos como revisiones y anuncios, y trabajos derivados como traducciones. Observe que esta restricci on no est a prevista para prohibir el cobro por el servicio de impresi on o copia del documento A los docentes se les recomienda usar este documento como recurso de clase; sin embargo el autor apreciar a ser noticado en este caso.

Prefacio
Prop osito
El prop osito de este libro es dar la lector un mejor entendimiento de c omo trabajan realmente los computadores a un nivel m as bajo que los lenguajes de alto nivel como Pascal. Teniendo un conocimiento profundo de c omo trabajan los computadores, el lector puede ser m as 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 un ense nan a programar el procesador 8086 que us o el PC original en 1981. El procesador 8086 s olo soporta el modo real. En este modo, cualquier programa puede acceder a cualquier direcci on de memoria o dispositivo en el computador. Este modo no es apropiado para un sistema operativo multitarea seguro. Este libro, en su lugar discute c omo 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 on de memoria. Hay varias razones para usar el modo protegido 1. Es m as f acil de programar en modo protegido que en el modo real del 8086 que usan los otros libros. 2. Todos los sistemas operativos de PC se ejecutan en modo protegido. 3. Hay disponible software libre que se ejecuta en este modos. La carencia de libros de texto para la programaci on en ensamblador de PC para modo protegido es la principal raz on por la cual el autor escribi o este libro. C omo 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 tambi en discute c omo usar el c odigo 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

ii

PREFACIO

plataformas se pueden encontrar en mi sitio web: http://www.drpaulcarter.com/pcasm. Debe descargar el c odigo de los ejemplos, si desea ensamblar y correr los muchos ejemplos de este tutorial. Tenga en cuenta que este libro no intenta cubrir cada aspecto de la programaci on en ensamblador. El autor ha intentado cubrir los t opicos m as importantes que todos los programadores deber an tener

Reconocimientos
El autor quiere agradecer a los muchos programadores alrededor del mundo que han contribuido al movimiento de Software Libre. Todos los programe y a un este libro en s mismo fueron producidos usando software libre. El autor desear a agradecerle especialmente a John S. Fine, Simon Tatham, Julian Hall y otros por desarrollar el ensamblador NASM ya que todos los ejemplos de este libro est an basados en el; a DJ Delorie por desarrollar el compilador usado de C/C++ DJGPP; la numerosa gente que ha contribuido al compilador GNU gcc en el cual est a basado DJGPP; a Donald Knuth y A otros por desarrollar los lenguajes de composici on de textos TEX y L TEX 2 que fueron usados para producir este libro; a Richar Stallman (fundador de la Free Software Fundation), Linus Torvalds (creador del n ucleo de Linux) y a otros que han desarrollado el software que el autor ha usado para producir este trabajo. Gracias a las siguientes personas por correcciones: John S. Fine Marcelo Henrique Pinto de Almeida Sam Hopkins Nick DImperio Jeremiah Lawrence Ed Beroset Jerry Gembarowski Ziqiang Peng Eno Compton Josh I Cates Mik Miin Luke Wallis

iii Gaku Ueda Brian Heward Chad Gorshing F. Gotti Bob Wilkinson Markus Koegel Louis Taber Dave Kiddell Eduardo Horowitz S ebastien Le Ray Nehal Mistry Jianyue Wang Jeremias Kleer Marc Janicki

Recursos en Internet
P agina del autor P agina de NASM en SourceForge DJGPP Ensamblador con Linux The Art of Assembly USENET Documentaci on de Intel

http://www.drpaulcarter.com/ http://sourceforge.net/projects/nasm/ http://www.delorie.com/djgpp http://www.linuxassembly.org/ http://webster.cs.ucr.edu/ comp.lang.asm.x86 http://developer.intel.com/design/Pentium4/documentation.ht

Comentarios
El autor agradece cualquier comentario sobre este trabajo. E-mail: WWW: pacman128@gmail.com http://www.drpaulcarter.com/pcasm

iv

PREFACIO

Cap tulo 1

Introducci on
1.1. Sistemas de numeraci on

La memoria en un computador est a compuesta de n umeros. La memoria del computador no almacena estos n umeros en decimal (base 10). Porque se simplica mucho el hardware, los computadores almacenan toda la informaci on en binario (base 2). Primero haremos una revisi on del sistema decimal.

1.1.1.

Decimal

Los n umeros con base 10 est an compuestos de 10 posibles d gitos (0-9). Cada d gito de un n umero tiene una potencia de 10 asociada con el, basada en su posici on en el n umero. Por ejemplo: 234 = 2 102 + 3 101 + 4 100

1.1.2.

Binario

Los n umeros en base dos est an compuestos de dos posibles d gitos (0 y 1). Cada d gito de un n umero tiene una potencia de 2 asociada con el basada en su posici on en el n umero. Por ejemplo: 110012 = 1 24 + 1 23 + 0 22 + 0 21 + 1 20 = 16 + 8 + 1 = 25 Esto muestra c omo los n umeros binarios se pueden convertir a decimal. El Cuadro 1.1 muestra c omo se representan los primeros n umeros en binario.

2 Decimal 0 1 2 3 4 5 6 7 Binary 0000 0001 0010 0011 0100 0101 0110 0111

CAP ITULO 1. INTRODUCCION Decimal 8 9 10 11 12 13 14 15 Binary 1000 1001 1010 1011 1100 1101 1110 1111

Cuadro 1.1: Decimal de 0 a 15 en binario No hay carry antes 0 1 1 +1 +0 +1 1 1 0 c S hay carry antes 0 1 1 +1 +0 +1 0 0 1 c c c

0 +0 0

0 +0 1

Figura 1.1: Suma binaria (c es carry )

La Figura 1.1 muestra c omo se suman los d gitos binarios individuales 110112 (bits). Ac a sigue un ejemplo: +100012 1011002 Si uno considera la siguiente divisi on decimal: 1234 10 = 123 r 4 podemos ver que esta divisi on suprime el d gito m as a la derecha de n umero y desplaza los otros d gitos una posici on a la derecha. Dividiendo por dos hacemos una operaci on similar, pero para los d gitos binarios de un n umero. Consideremos la siguiente divisi on binaria1 : 11012 102 = 1102 r 1 Este hecho se puede usar para convertir un n umero decimal a su representaci on equivalente en binario como muestra la Figura 1.2. Este m etodo encuentra primero el bit del extremo derecho, llamado bit menos signicativo (lsb). El bit del extremo izquierdo es llamado bit m as signicativo (msb). La unidad b asica de memoria est a compuesta de 8 bits y es llamado byte
1 El sub ndice 2 se usa para mostrar que el n umero est a representado en binario no en decimal

1.1. SISTEMAS DE NUMERACION

Decimal 12 2 = 6 r 0 62=3r 0 32=1r 1 12=0r 1

Binary 1100 10 = 110 r 0 110 10 = 11 r 0 11 10 = 1 r 1 1 10 = 0 r 1

25 2 = 12 r 1 11001 10 = 1100 r 1

As 2510 = 110012 Figura 1.2: Conversi on a decimal

1.1.3.

Hexadecimal

Los n umero hexadecimales tienen base 16. Los hexadecimales (o hex ) se pueden usar como una representaci on resumida de los n umeros binarios. Los n umeros hexadecimales tienen 16 d gitos posibles. Esto crea un problema ya que no hay s mbolos para estos d gitos adicionales despu es del nueve. Por convenci on se usan letras para estos d gitos adicionales. Los 16 d gitos hexadecimales son: 0-9 y luego A, B, C, D, E, F. El d gito A equivale a 10 en decimal, B es 11 etc. Cada d gito de un n umero hexadecimal tiene una potencia de 16 asociada con el. Por ejemplo: 2BD16 = 2 162 + 11 161 + 13 160 = 512 + 176 + 13 = 701

Para convertir de decimal a hex use la misma idea que la usada para la conversi on binaria excepto que se divide por 16. Vea la Figura 1.3 para un ejemplo. La raz on por la cual los hexadecimales son u tiles es que hay una manera f acil para convertir entre hex y binario. Los n umero binarios se tornan largos y molestos r apidamente. Los hex son una manera mucho m as compacta de representar los n umeros binarios. Para convertir un n umero hexadecimal a binario simplemente convierta cada d gito hexadecimal a un n umero binario de 4 bits. Por ejemplo, 24D16 se convierta a 0010 0100 11012 . 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 oneo. Convertir de binario a hex es as de f acil. Uno hace el proceso

CAP ITULO 1. INTRODUCCION

589 16 = 36 r 13 36 16 = 2 r 4 2 16 = 0 r 2

As 589 = 24D16 Figura 1.3: inverso, convierte cada segmento de 4 bits a hexadecimal comenzando desde el extremo derecho, no desde el izquierdo, del n umero binario. Esto asegura 2 que el segmento de 4 bits es correcto . Ejemplo: 110 6 0000 0 0101 5 1010 A 0111 7 11102 E16

Un n umero de 4 bits es llamado nibble . As cada d gito hexadecimal corresponde a un nibble. Dos nibbles conforman un byte y por lo tanto un byte puede ser representado por dos d gitos hexadecimales. Los valores de un byte van de 0 a 11111111 en binario, 0 a FF en hex y 0 a 255 en decimal.

1.2.
1.2.1.

Organizaci on del computador


La Memoria

La memoria es medida La unidad b asica de memoria es el byte. Un computador con 32 Mega en unidades de kilobytes bytes de memoria puede almacenar aproximadamente 32 millones de bytes ( 210 = 1024 bytes), mega de informaci on. Cada byte est a etiquetado por un n umero u nico conocido bytes ( 220 = 1048576 como su direcci o n. Tal como lo muestra la Figura 1.4. bytes) y giga bytes ( 230 = 1073741824 bytes). Direcci on 0 1 2 3 4 5 6 7

Memoria

2A

45

B8

20

8F

CD

12

2E

Figura 1.4: Direcciones de Memoria

A menudo la memoria se usa en trozos m as grandes que un byte. en la arquitectura del PC, los nombres que se le han dado a estas secciones de memoria m as grandes se muestran en la Tabla 1.2.
2 Si no es claro porque el punto de inicio hace la diferencia, intente convertir el ejemplo comenzando desde la izquierda

DEL COMPUTADOR 1.2. ORGANIZACION word double word quad word paragraph 2 bytes 4 bytes 8 bytes 16 bytes

Cuadro 1.2: Unidades de memoria

Todos los datos en la memoria son num ericos. Los caracteres son almacenados usando c odigos de caracteres que traduce un n umero en un car acter. Uno de los c odigos de caracteres es conocido como ASCII (American Standar Code for Information Interchange). Un nuevo c odigo, m as completo, que est a reemplazando al ASCII es el Unicode. Una diferencia clave entre los dos c odigos es que el ASCII usa un byte para codicar un car acter, pero Unicode usa dos bytes (o una palabra ) por car acter. Por ejemplo ASCII decodica el byte 4116 (6510 ) en la A may uscula. Unicode la codica con la palabra 004116 . Ya que ASCII usa un byte est a limitado a s olo 256 caracteres 3 diferentes. Unicode ampl a los valores ASCII a palabras y permite que se representen muchos m as caracteres. Esto es importante para representar los caracteres de todas las lenguas del mundo.

1.2.2.

La CPU

La Unidad Central de Procesamiento (CPU) es el dispositivo f sico que ejecuta las instrucciones. Las instrucciones que ejecuta la CPU son por lo general muy simples. Las instrucciones pueden requerir datos que est en 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 m as r apido que en la memoria. Sin embargo el n umero de registros en la CPU es limitado, as el programador debe tener cuidado de dejar s olo los datos que est e usando en los registros. Las instrucciones que un tipo de CPU ejecuta las hace en lenguaje de m aquina. Los programas en lenguaje de m aquina tienen una estructura mucho m as b asica que los lenguajes de alto nivel. Las instrucciones en lenguaje de m aquina son codicadas como n umeros no en formatos de texto amigables. Una CPU debe estar en capacidad de decodicar una instrucci on de prop osito muy r apidamente para correr ecientemente. Los lenguajes de m aquina son dise nados con este objetivo en mente , no para ser f acilmente descifrados por humanos. Los programas escritos en otros lenguajes deben ser convertidos en lenguaje de m aquina nativo para que se ejecute en el computador. Un compilador es un programa que traduce programas escritos en
3

De hecho ASCII s olo usa los 7 bits m as bajos y s olo tiene 128 valores diferentes

CAP ITULO 1. INTRODUCCION

GHz signica giga Hertz o mil millones de ciclos por segundo. Una CPU de 1.5 GHz tiene mil quinientos millones de pulsos de reloj por segundo.

un lenguaje de programaci on a el lenguaje de m aquina de una arquitectura en particular de un computador. En general cada tipo de CPU tiene su propio y u nico lenguaje de m aquina. 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 on de las instrucciones.. El reloj pulsa a una frecuencia ja conocida como velocidad del reloj. Cuando Ud. compra un computador de 1.5 GHz, la frecuencia de su reloj es 15.GHz. Simplemente toca a una raz on constante, la electr onica de la CPU usa un ritmo para realizar sus operaciones correctamente, como el ritmo de un metr onomo para la ejecuci on de m usica al ritmo correcto. El n umero de toques (o como ellos llaman com unmente ciclos ) que una instrucci on requiere depende de la instrucci on anterior y de otros factores tambi en.

1.2.3.

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 m aquina b asico. Sin embargo los miembros m as recientes ampl an grandemente las caracter sticas. 8888,8086: Estas CPU desde el punto de vista de la programaci on son iguales. Ellas fueron las CPUs usadas en las primeras PC. Ellos usan varios registros AX, BX, CX, DX, SI, DI, BP, SP, CS, DS, SS, ES, IP, FLAG. Ellas solo soportan hasta 1 Mega byte de memoria y s olo opera en modo real. En este modo un programa puede acceder a cualquier direcci on de memoria, a un a la memoria de otros programas Esto hace la depuraci on y seguridad muy dif cil. Tambi en la memoria del programa tiene que ser dividida en segmentos. Cada segmento no puede ser m as largo que 64 KB 80286: Esta CPU se usa en los PC tipo AT. Agrega unas instrucciones nuevas al lenguaje de m aquina base del 8080/86. 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. Sin embargo los programas todav a est an divididos en segmentos que no pueden ser m as grandes de 64K. 80386: Esta CPU es una gran ampliaci on del 80286. Primero extiende los registros para almacenar 32 bits ((EAX, EBX, ECX, EDX, ESI, EDI, EBP, ESP, EIP) y a nade dos nuevos registros de 16 bits FS y GS. Tambi en a nade un nuevo modo protegido de 32 bits. En este modo pueden acceder hasta 4 Gigabyes. Los programas otra vez est an divididos en

DEL COMPUTADOR 1.2. ORGANIZACION AX AH AL Figura 1.5: El registro AX

segmentos, pero ahora cada segmento tambi en puede ser hasta de 4 Giga bytes en tama no 80486/Pentium/Pentium Pro: Estos miembros de la familia 80x86 a naden muy pocas caracter sticas nuevas. Ellos principalmente aceleran la ejecuci on de las instrucciones. Pentium MMX: Este procesador a nade instrucciones MMX (eXtensiones MultiMedia) al Pentium. Estas instrucciones pueden acelerar instrucciones comunes gr acas. item[Pentium II:] Este es el procesador Pentium Pro con las instrucciones MMX a nadidas (El pentium III es esencialmente s olo un Pentium II r apido.

1.2.4.

Registros de 16 bits del 8086

La CPU original 8086 suministra 4 registros de 16 bits de prop osito general AX, BX, CX y DX. Cada uno de esos registros puede ser descompuesto en los registros AL y AH que muestra la Figura 1.5. El registro AH contiene los 8 bits superiores de AX t AL contiene los 8 bits bajos de AX. A menudo AH y AL son usados como registros independientes de 8 bits sin embargo cambiando el valor de AX cambiar a AH y AL y viceversa. Los registros de prop osito general son usados en muchos movimientos de datos e instrucciones aritm eticas. Hay dos registros de ndice de 16 bits SI y DI. Ellos son a menudo usados como apuntadores, pero pueden ser usados para muchos de los mismos prop ositos como los registros generales. Sin embargo, ellos no se pueden descomponer en registros de 8 bits. Los registros de 16 bits BP y SP son usados para se nalar a los datos en la pila y son llamados Apuntador Base (Base Pointer) y apuntador a la pila (Stack Pointer), respectivamente. Ellos se discutir an luego. Los registros de 16 bits CS, DS, SS y ES son registros de segmento. Ellos se nalan qu e memoria es usada por diferentes partes de un programa. CS signica segmento de c odigo (code segment), DS segmento de datos (data segment), SS Segmento de la pila (Stack Segment) y ES segmento extra (Extra Segment). ES es usado como un registro temporal. Los detalles de estos registros est an en las Secciones 1.2.6 y 1.2.7.

CAP ITULO 1. INTRODUCCION

El registro IP Apuntador a la instrucci on (instruction pointer) es usado con el registro CS para obtener la direcci on de la siguiente instrucci on a ser ejecutada por la CPU. Normalmente cuando se ejecuta una instrucci on IP avanza hasta se nalar a la siguiente instrucci on en memoria. 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. Normally, as an instruction is executed, IP is advanced to point to the next instruction in memory. El registro FLAGS almacena informaci on importante sobre los resultados de una instrucci on anterior. Estos resultados son almacenados como bits individuales en el registro. Por ejemplo el bit Z es 1 si el resultado de la instrucci on anterior fue cero o 0 si el resultado no fue cero. No todas las instrucciones modican bits en FLAGS, consulte la tabla en el ap endice para ver c omo instrucciones espec cas afectan el registro FLAGS

1.2.5.

Registros de 32 bits del 80386

El 80386 y los procesadores posteriores tienen registros extendidos. Por ejemplo el registro de 16 bits AX se extendi o para se de 32 bits. Para la compatibilidad con sus predecesores, AX se reere al registro de 16 bits y EAX se usa para referirse al registro extendido de 32 bits. AX son los 16 bits inferiores de EAX tal como AL son los 8 bits inferiores de AX (y EAX). No hay forma de acceder directamente a los 16 bits superiores de EAX Los otros registros extetendidos son EBX, ECX, EDX, ESI and EDI. Muchos de los otros registros se extienden tambien BP se convierte en EBP, SP se convierte en ESP , FLAGS en EFLAGS e IP en EIP. Sin embargo son diferentes los registros de ndice y los de prop osito general, en el modo protegido de 32 bits (discutidos abajo) s olo se usan las versiones extendidas de estos registros. Los registros de segmento contin uan siendo de 16 bits en el 80386. Hay tambi en dos nuevos registros de segmento: FS y GS. Sus nombres no significan nada. Ellos son registros adicionales para segmentos temporales (como ES). Una de las deniciones del t ermno word se reere a el tam no del registro de datos de la CPU. Para la familia 80x86, el t ermino es ahora un poco confuso. En la Tabla 1.2, uno ve que word est a denida para ser 20 bytes (o 16 bits). Este fue el signicado que se le dio, cuando se lanz o la primera vez el 8086. Cuando se desarroll o el 80386, se decidi o dejar la denici on de word sin cambio, auque el tama no del registro cambi o.

1.2.6.
De d onde viene el infame l mite de 640K de DOS? La BIOS requerida algunode 1M para el c odigo y para los dispositivos de hardware como la pantalla de video

Modo Real

En el modo real la memoria est a limitada a s olo 1 mega byte (220 bytes)

DEL COMPUTADOR 1.2. ORGANIZACION

Las direcciones v alidas est an desde 0000 hasta FFFFF. (en hexadecimal) Estas direcciones requieren un n umero de 20 bits Obviamente un n umero de 20 bits no cabr a en ning un registro de 16 bits. Intel solucion o este problema usando 2 valores de 16 bits para determinar una direcci on. El primer valor de 16 bits es llamado selector. Los valores del selector deben estar almacenados en registros de segmento El segundo valor de 16 bits es llamado desplazamiento (oset) La direcci on f sica referenciada por un par selector:desplazamiento es calculada por la f ormula: 16 selector + oset multiplicar por 16 en hexadecimal es muy f acil, es s olo a nadir un 0 a la derecha del n umero. Por ejemplo la direcci on f sica referenciada por 047C:0048 est a dada por: 047C0 +0048 04808 En efecto, el valor selector es un n umero p arrafo (vea la Tabla 1.2). direcciones reales segmentadas tienen desventajas: Un s olo valor de selector s olo puede referenciar 64 K de memoria (el l mite superior del desplazamiento de 16 bits). Qu e pasa si un programa tiene m as de 64 K de c odigo? Un solo valor en CS no se puede usar para toda la ejecuci on del programa. El programa se debe dividir en secciones (llamadas segmentos menores de 64 K en tama no. Cuando la ejecuci on se mueve de un segmento a otro los valores de CS se deben cambiar. Esto puede ser muy inc omodo Cada byte de memoria no tiene una sola direcci on segmentada. La direcci on f sica 04804 puede ser referenciada por 047C:0048, 047D:0038, 0047E:0028 o 047B:0058. Esto puede complicar la comparaci on de direcciones segmentadas.

1.2.7.

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, un valor de selector es un n umero de p arrafo de memoria f sica. En el modo protegido un valor selector es un ndice en una tabla de descripci on. En ambos modos, los programas son divididos en segmentos. En modo real estos segmentos est an en posiciones jas en la memoria f sica y el selector denota el n umero de p arrafo de comienzo del segmento. En modo protegido los segmentos no

10

CAP ITULO 1. INTRODUCCION

est an en posiciones jas en la memoria f sica. De hecho no tiene que estar todo el segmento en memoria. El modo protegido usa una t ecnica llamada memoria virtual . La idea b asica de un sistema de memoria virtual, es dejar s olo los datos y el c odigo que los programas est an usando en un momento dado. Otros datos y c odigo son almacendos temporalmente en el disco hasta que ellos se necesiten de nuevo. Cuando retorna un segmento a la memoria del disco, es muy probable que se coloque en un area diferente de memoria en el que estuvo antes de ser enviada al disco. Todo esto es hecho transparementemente por el sistema operativo. El programa no se tiene que escribir de otra manera para que la memoria virtual trabaje. En el modo protegido a cada segmento se le asigna una entrada en una tabla de descriptores. Esta entrada tiene toda la informaci on que el sistema necesita conocer sobre el segmento. Esta informaci on incluye: si est a actualemente en memoria, si es as d onde est a, permiso de acceso (ejem: s olo lectura). El ndice de la entrada del segmento es el valor del selector que est a almacendo en los registros de segmento. Un conocido columnista de Una gran desventaja del modo protegido es que los desplazamientos est an PC llam o al 286 cerebro a un en cantidades de 16 bits Como una consecuencia de esto, los tama nos muerto. de los segmentos est an todav a limitados a un m aximo de 64K. Esto hace problem atico el uso de arreglos grades.

1.2.8.

Modo protegido de 32 bits

El 80386 introdujo el modo protegido de 32 bits. Hay dos grandes diferencias entre los modos protegidos de un 386 de 32 bits y un 286 de 16 bits 1. Los desplazamientos se ampl an a 32 bits. Esto permite un rango de desplazamiento hasta 4 billones. As los segmentos pueden tener tama nos hasta de 4 gigabytes. 2. Los segmentos pueden ser divididos en unidades m as peque nas de 4K llamadas p aginas . El sistema de memoria virtual trabaja ahora con p aginas en lugar de segmentos. Esto signica que s olo partes de un segmento pueden estar en memoria a la vez. En el modo de 16 bits del 286 o todo el segmento est a en memoria o no est a. Esto no es pr actico con los grandes segmentos que permite el modo de 32 bits. En Windows 3.x el modo standar se reere al modo protegido de 16 bits del 286 y el modo ampliado se reere al modo de 32 bits.

1.2.9.

Interrupciones

Algunas veces el ujo ordinario de un programa debe ser interrumpido para procesar eventos que requieren una respuesta r apida. El hardware de

1.3. LENGUAJE ENSAMBLADOR

11

un computador provee un mecanismo llamado interrupci on para manipular estos eventos. Por ejemplo cuando se mueve el rat on la interrupci on de hardware del rat on es el programa actual para manejar el movimiento del rat on (para mover el cursor del mouse, etc) Las interrupciones hacen que el control se pase a un manipulador de interrupciones. Los manipuladores de interrupciones son rutinas que procesan la interrupci on. A cada tipo de interrupci on se le asigna un n umero entero. 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 interrupci on. El n umero de la interrupci on es escencialmente un ndice en esta tabla. Las interrupciones externas son levantadas desde el exterior de la CPU (el rat on es un ejemplo de esto). Muchos dispositivos de E/S levantan interrupciones (teclado, temporizador, disco duro CD ROM y tarjetas de sonido) Las interrupciones internas son levantadas desde la CPU, desde una instrucci on de error o desde una instrucci on de interrupci on. Las instrucciones de error tambi en se llaman trampas. Las instrucciones generadas desde la instrucci on de interrupci on son llamadas interrupciones de sofware. DOS usa estas interrupciones paa implementar su API (Interfaz de programas de Aplicaci on) Sistema operativos m as modernos (como Windows y Linux) us4 an una interfaz basada en C Muchos manipuladores de interrupci on devuelven el control al programa interrumpido cuando ella culmina. Ella restaura todos los registros con los mismos valores que ten an antes que ocurriera la interrupci on. Las trampas generalmente no retornan. A menudo ellas acaban el programa.

1.3.
1.3.1.

Lenguaje ensamblador
Lenguaje de m aquina

Cada tipo de CPU entiende su propio lenguaje de m aquina. Las instrucciones en lenguaje de m aquina son n umeros almacenados como bytes en memoria. Cada instrucci on tiene su propio y u nico c odigo llamado c odigo de operaci on u opcode. Las instrucciones del procesador 80X86 var an en tama no. El opcode est a siempre al inicio de la instrucci on. Muchas instrucciones incluyen tambi en datos (ver constantes o direcciones) usados por las instrucciones. El lenguaje de m aquina es muy dif cil de programar directamente. Descifrar el signicado de las instrucciones codicadas num ericamente es tedioso para los humanos. Por ejemplo la instrucci on para sumar los registros EAX y EBX y almacenar el resultado en EAX est a codicada por los siguientes c odigos hexadecimales
4

Sin embargo, ellas pueden usar una interfaz de bajo nivel (a nivel del kernel)

12 03 C3

CAP ITULO 1. INTRODUCCION

Esto no es obvio. Afortunadamente, un programa llamado ensamblador puede hacer este aburrido trabajo para el programador.

1.3.2.

Lenguaje ensamblador

Un programa Escrito en lenguaje ensamblador es almacenado como texto (tal como programas de alto nivel). Cada instrucci on representa exactamente una instrucci on de la m aquina. Por ejemplo, la instrucci on de suma descrita arriba podr a ser representada en lenguaje ensambaldor como: add eax, ebx Ac a el signicado de la instrucci on es mucho m as claro que el c odigo de la m aquina. La palabra add es el nem onico nem onico para la instrucci on de suma . La forma general de una instrucci on 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. Los compiladores son programas que hacen conversiones similares para lenguajes de programaci on de alto nivel. Un ensamblador es mucho m as simple que un Les tom o varios a nos a compilador. Cada instrucci on de lenguaje ensamblador representa una sola los cient cos de la com- instrucci on de la m aquina. Las instrucciones de un lenguaje de alto nivel son putaci on imaginarse c omo mucho m as complejas y pueden requerir muchas instrucciones de m aquina. escribir un compilador 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. Trasladar programas entre arquitecturas de computador diferentes es mucho m as dif cil que en un lenguaje de alto nivel. En los ejemplos de este libro se usa Netwide Assembler o NASM . Est a disponible libremente en internet (vea el prefacio para la URL). Los ensambladores m as comunes son el ensamblador de Microsoft (MASM) y el de Borland (TASM) . Hay algunas diferencias en la sintaxis del ensamblador de NASM, MASM y TASM .

1.3.3.

Operandos de las instrucciones

Los c odigos de las instrucciones de m aquina tienen una variedad de tipos y operandos; sin embargo, en general cada instrucci on en si misma tiene un n umero jo de operandos (0 a 3). Los operandos pueden tener los siguientes tipos:

1.3. LENGUAJE ENSAMBLADOR

13

registro: Estos operandos se reeren directamente al contenido de los registros de la CPU. memoria: Estos se reeren a los datos en la memoria. La direcci on de los datos puede ser una constante ja en la instrucci on o puede ser calculada usando los valores de los registros. Las direcciones son siempre desplazamientos relativos al comienzo de un segmento. immediato: Estos son valores jos que est an listados en la instrucci on en s misma. Ellos son almacenados en la instrucci on en si misma (en el segmento de c odigo), no en el segmento de datos. implicado: Estos operandos no son mastrados expl citamente. Por ejemplo, la instrucci on de incremento a nade uno a un registro o a memoria. El uno est a impl cito.

1.3.4.

instrucciones b asicas

La instrucci on esencial es MOV . Ella translada datos de un lugar a otro (como el operador de asignaci on en un lenguaje de alto nivel). Toma dos operandos: mov dest, src El dato especicado por src es copiado a dest. Una restricci on es que los dos operandos no pueden ser operandos de memoria. Esto se nala otra peculiaridad del ensamblador. Hay a menudo algunas reglas arbitrarias sobre c omo se usan las instrucciones. Los operandos deben tener el mismo tama no. El valor de AX no puede ser almacenado en BL. Ac a hay un ejemplo(los ; inician un comentario) mov mov add add sub sub eax, 3 bx, ax eax, 4 al, ah ; almacena 3 en el registro EAX (3 es el operando inmediato) ; almacena el valor de AX en el registro BX ; eax = eax + 4 ; al = al + ah

La instrucci on ADD se usa para sumar enteros.

La instrucci on SUB resta enteros. bx, 10 ; bx = bx - 10 ebx, edi ; ebx = ebx - edi

Las instrucciones INC y DEC incrementan o decrementan valores en uno. Ya que el uno es un operando impl cito, el c odigo de de m aquina para INC y el DEC es m as peque no que los de las instrucciones ADD y SUB. inc dec ecx dl ; ecx++ ; dl--

14

CAP ITULO 1. INTRODUCCION

1.3.5.

Directivas

Una directiva es un articio del ensamblador no de la CPU. Ellas se usan generalmente para decirle al ensamblador que haga alguna cosa o informarle al ensamblador de algo. Ellas no se traducen en c odigo de m aquina. Los usos comunes de las directivas son: Denir constantes Denir memoria para almacenar datos en ella Denir la memoria para almacenar datos en ella Agrupar la memoria en segmentos Inclu r c odigo fuente condicionalmente Inclu r otros archivos El c odigo de NASM pasa a trav es de un preprocesador tal como C. Tiene muchas de las ordenes del preprocesador tal como C. Sin embargo las directivas del preprocesador de NASM comienzan con un como en C. directiva equ La directiva equ se puede usar para denir un s mbolo. Los s mbolos son constantes con nombre que se pueden emplear en el programa ensamblador. El formato es: s mbolo equ valor Los valores de los s mbolos no se pueden redenir posteriormente. La directiva %dene Esta directiva es parecida a la #dene de C. Se usa normalmente para denir macros tal como en C. %define SIZE 100 mov eax, SIZE El c odigo de arriba dene un macro llamado size y muestra su uso en una instrucci on MOV. Los macros son m as exibles que los s mbolos de dos maneras. Los macros se pueden redenir y pueden ser m as que simples constantes n umericas.

1.3. LENGUAJE ENSAMBLADOR Unidad byte word double word quad word ten bytes Letra B W D Q T

15

Cuadro 1.3: Letras para las directivas RESX y DX Directivas de datos Las directivas de datos son usadas en segmentos de datos para denir espacios de memoria. Hay dos formas en que la memoria puede ser reservada. La primera es solo denir el espacio para los datos; la segunda manera dene el espacio y el valor inicial. El primer m etodo usa una de las directivas RESX. La X se reemplaza con una letra que determina el tama no del objeto (u objetos) que ser a almacenados. La tabla 1.3 muestra los valores posibles. El segundo m etodo (que dene un valor inicial tambi en) usa una de las directivas DX. Las X son las mismas que las de la directiva RESX. Es muy com un marcar lugares de memoria con etiquetas. Las etiquetas le permiten a uno referirse f acilmente a lugares de la memoria en el c odigo. Abajo hay varios ejemplos. 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 12 (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 odigo ASCII para A (65)

Las comillas dobles o simples se interpretan igual. Las deniciones consecutivas de datos se almacenar an secuencialmente en memoria. Esto es, la palabra L2 se almacenar a inmediatamente despu es que la L1. Se pueden denir tambi en secuencias de memoria. L9 L10 L11 db db db 0, 1, 2, 3 "w", "o", "r", d, 0 word, 0 ; define 4 bytes ; define una cadena tipo C = "word" ; igual que L10

La directiva DD se puede usar para denir o enteros o constantes de punta otante de presici on simple.5 Sin embargo DQ solo se puede usar para denir constantes de punta otante de doble precisi on.
5

Punto otante de presici on simple es equivalente a la variable oat en C.

16

CAP ITULO 1. INTRODUCCION

Para secuencias largas la directiva TIMES de NASM es a menudo u til. Esta directiva repite su operando un n umero especicado de veces por ejemplo: L12 L13 times 100 db 0 resw 100 ; equivalente a 100 veces db 0 ; reserva lugar para 100 palabras

Recuerde que las etiqueta pueden ser usadas para referirse a datos en el c odigo. Si se usa una etiqueta esta es interpretada como la direcci on (o desplazamiento) del dato. Si la etiqueta es colocada dentro de par entesis cuadrados ([]), se interpreta como el dato en la direcci on. En otras palabras, uno podr a pensar de una etiqueta como un apuntador al dato y los par entesis cuadrados como la des referencia al apuntador tal como el asterisco lo hace en C (MSSM y TASM siguen una convenci on diferente). En el modo de 32 bits las direcciones son de 32 bits. Ac a hay algunos ejemplos.
1 2 3 4 5 6 7

mov mov mov mov add add mov

al, [L1] eax, L1 [L1], ah eax, [L6] eax, [L6] [L6], eax al, [L6]

; ; ; ; ; ; ;

copia el byte que est a en L1 en AL EAX = direcci on 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. El ensamblador no recuerda el tipo de datos al cual se reere la etiqueta. De tal forma que el programador debe estar seguro que usa la etiqueta correctamente. Luego ser a com un almacenar direcciones de datos en registros y usar los registros como una variable apuntador en C. Una vez m as no se verica que el apuntador se use correctamente. De este modo el ensamblador es mucho m as propenso a errores a un que C. Considere la siguiente instrucci on: mov [L6], 1 ; almacena 1 en L6

Esta instrucci on produce un error de tama no no especicado. Por qu e? Porque el ensamblador no sabe si almacenar el 1 como byte, palabra o palabra doble. Para denir esto, se a nade un especicador de tama no . mov dword [L6], 1 ; almacena 1 at L6

Esto le dice al ensamblador que almacene un 1 en la palabra doble que comienza en L6. Otros especicadores son: BYTE, WORD, QWORD Y TWORD. 6
6 TWORD dene un area de memoria de 10 bytes. El coprocesador de punto otante usa este tipo de dato.

1.3. LENGUAJE ENSAMBLADOR print int print char print string imprime en la pantalla el valor del entero almacendo en EAX imprime en la pantalla el caracter cuyo c odigo ASCII est e almacendo en AL imprime en la pantalla el contenido de la cadena en la direcci on almacenada en EAX. La cadena debe ser tipo C, terminada en NULL). imprime en pantalla el caracter de nueva l nea. lee un entero del teclado y lo almacena en el registro. lee un solo caracter del teclado y almacena el c odigo ASCII en el registro EAX.

17

print nl read int read char

Cuadro 1.4: Rutinas de E/S en ensamblador

1.3.6.

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, como C, proveen bibliotecas normalizadas de rutinas que suministran una interfas de programaci on simple y uniforme para la dE/ S. Los lenguajes ensamblador no disponen de bibliotecas normalizadas. 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. Es muy com un que se interfacen rutinas de ensamblador con C. 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. Sin embargo uno debe conocer las reglas que usa C para pasar informaci on entre rutinas. Estas reglas son muy complicadas para cubrir ac a (ellas se ver an luego) . Para simplicar la E/S el autor ha desarrollado sus propias rutinas que ocultan las complejas reglas de C y provee una interfas mucho m as simple. La tabla 1.4 describe las rutinas suministradas. Todas las rutinas preservan el valor de todos los registros , excepto las rutinas de lectura. Estas rutinas modican el valor del registro EAX. Para usar estas rutinas uno debe incluir un archivo con la informaci on que el ensamblador necesita usarlas. Para incluir un archivo en NASM use la directiva del preprocesador %include. La siguiente l nea incluye el archivo necesario para las rutinas de E/S hechos por el autor http://www.drpaulcarter.com/pcasm: %include "asm_io.inc" Para usar una de las rutinas print, uno carga EAX con el valor correcto y usa la instrucci on Call para invocarla.La instrucci on Call es equivalente a un llamado de funci on en un lenguaje de alto nivel. Hace un salto en la ejecuci on hacia otra secci on de c odigo pero despu es retorna al origen luego

18

CAP ITULO 1. INTRODUCCION

que la rutina a culminado. El programa muestra varios ejemplos de llamadas de estas rutinas de E/S.

1.3.7.

Depuraci on

La biblioteca del autor tambi en contiene algunas rutinas u tiles para depurar los programas. Estas rutinas de depuraci on muestran informaci on sobre el estado del computador sin modicar su estado. Estas rutinas son en realidad macros que muestran el estado de la CPU y luego hacen un llamado a una subrutina. Los macros est an denidos en el archivo code asm io.inc discutido antes. Los matros se usan como instrucciones normales. Los operandos de los macros se separan con comas. Hay cuatro rutinas de depuraci on llamadasdump regs, dump mem, dump stack and dump math; Ellas muestran los valores de los registros, memoria, pila y el coprocesador matem atico respctivamente dump regs Este macro imprime los valores de los registros (en hexadecimal) del computador stdout (la pantalla). Tambi en imprime el estado 7 de los bits del registto FLAGS. Por ejemplo si la bandera cero es 1 se muestra ZF. Si es cero no semuestra nada. Torma un solo entero como par ametro que luego se imprime. Este entero se puede usar para distinguir la salida de diferentes ordenes dump regs. dump mem Este macro imprime los valores de una regi on de memoria (en hexadecimal) y tambi en como caracteres ASCII. Toma tres argumentos delimitados por comas. El primero es un entero que es usado para identicar la salida (tal cual como el argumento de dump regs). El segundo argumento es la direcci on a mostrar (esta puede ser una etiqueta). El u ltimo argumento es un n umero de l6 bytes para mostrar luego de la direccci on. La memoria mostrada comenzar a en el primer l mite un p arrafo antes de la direcci on solicitada. dump stack Este macro imprime los valores de la pila de la CPU (la pila se ver a en el cap tulo 4). La pila est a organizada como palabras dobles y est a rutina las mostrar a de esta forma. Toma tres par ametros separados por comas. El primero es un identicador entero (como umero de palabras dobles para mostrar dump regs). El segundo es el n luego de la direcci on que tenga almacenada el regisstro EBP, y el tercer argumento es el n umero de palabras dobles a imprimir sobre la direcci on de EBP. dump math Este macro imprime los valores de los registros del coprocesador matem atico. Toma un solo par ametro entero como argumen7

El cap tulo 2 discute este registro

1.4. CREANDO UN PROGRAMA int main() { int ret status ; ret status = asm main(); return ret status ; } Figura 1.6: c odigo de driver.c

19

to que se usa para identicar la salida tal como el argumento de dump regs lo hace.

1.4.

Creando un programa

Hoy d a no es com un crear un programa independiente escrito totalmente en lenguaje ensamblador. El ensamblador es usado para desarrollar ciertas rutinas cr tica. Por qu e? Es mucho m as f acil programar en un lenguaje de alto nivel que en ensamblador. Tambi en al usar ensamblador es muy dif cil transportar el programa a otras plataformas. De hecho es raro usar el ensamblador en todo. Por qu e alguien quisiera aprender ensamblador? 1. Algunas veces el c odigo escrito en ensamblador puede ser m as r apido y peque no que el c odigo generado por un compilador. 2. 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. 3. Aprender a programar en ensamblador le ayuda a uno a ganar un entendimiento profundo de c omo trabaja el computador. 4. Aprender a programar en ensamblador ayuda a entender mejor c omo trabajan los compiladores y los lenguajes de alto nivel como C. Los u ltimos dos puntos demuestran que aprender ensamblador puede ser u til a un si uno nunca programa en el m as. De hecho, el autor raramente programa en ensamblador pero usa las ideas aprendidas de el todos los d as.

1.4.1.

Primer programa

Los primeros programas en este texto comenzar an todos con un programa sencillo de C mostrado en la Figura 1.6. Simplemente llama otra funci on

20

CAP ITULO 1. INTRODUCCION

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

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22

1.4. CREANDO UN PROGRAMA


23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64

21

; ; Los datos no iniciados se colocan en el segmento .bss ; segment .bss ; ; Estas etiquetas se~ nalan a palabras dobles usadas para almacenar los datos ; de entrada ; input1 resd 1 input2 resd 1 ; ; El c odigo se coloca en el segmento .text ; segment .text global _asm_main _asm_main: enter 0,0 ; setup routine pusha mov call call mov mov call call mov mov add mov eax, prompt1 print_string read_int [input1], eax eax, prompt2 print_string read_int [input2], eax eax, [input1] eax, [input2] ebx, eax ; print out prompt

; lee un entero ; lo almacena en input1 ; print out prompt

; lee un entero ; lo almacena en input2 ; eax = dword en input1 ; eax += dword en input2 ; ebx = eax ; imprime los valores de los registros ; imprimer la memoria

dump_regs 1 dump_mem 2, outmsg1, 1

; ; ahora, se imprimen los resultados en una serie de pasos ;

22
65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82

CAP ITULO 1. INTRODUCCION mov call mov call mov call mov call mov call mov call call popa mov leave ret eax, outmsg1 print_string eax, [input1] print_int eax, outmsg2 print_string eax, [input2] print_int eax, outmsg3 print_string eax, ebx print_int print_nl

; se imprime el primer mensaje ; se imprime input1 ; se imprime el segundo mensaje ; se imprime input2 ; se imprime el tercer mensaje ; se imprime la suma (ebx) ; se imprime una nueva linea

eax, 0

; retorna a first.asm

La l nea 13 del programa dene una secci on del programa que especica la memoria al ser almacenada en el segmento de datos ( cuyo nombre es .data ). Solo los datos iniciados se deber an denir en este segmento. En las l neas 17 a 21 se declaran varias cadenas. Ellas ser an impresas con las bibliotecas de C y como tal deben estar terminadas con el caracter null (el c odigo ASCII 0). Recuerde que hay una gran diferencia entre 0 y 0. Los datos no iniciados deber an declararse en el segmento bss (llamado .bss en la l nea 26). Este segmento toma su nombre de un operador de ensamblador basado en UNIX que signica block started by simbol. Existe tambi en el segmento de la pila.Ser a discutido despu es. El segmento de c odigo es llamado .text por razones hist oricas. Ac a es donde se colocan las instrucciones. Observe que la etiqueta de la rutina principal (l nea 38) tiene un prejo de gui on bajo. Esto es parte de las convenciones de llamado de C. Esta convenci on especica las reglas que usa C cuando compila el c odigo. Es muy importante conocer esta convenci on cuando se interfaza C con ensamblador. Luego se presentara toda la convenci on, sin embargo por ahora uno solo necesita conocer que todos los s mbolos de C ( funciones y variables globales ) tienen un gui on bajo como prejo anexado a el por el compilador de C. (Esta regla es espec ca para Dos/windows, el compilador de C de linux no antepone nada a los nombres de los s mbolos). La directiva global en la l nea 37 le dice al ensamblador que tome la etiqueta asm main como global. Como en C, las etiquetas tienen un alcance interno por defecto. Esto signica que solo el c odigo en el mismo m odulo

1.4. CREANDO UN PROGRAMA

23

puede usar la etiqueta. La directiva global da a la (s) etiqueta (s) espec cadas de (s) un alcance externo. Este tipo de etiqueta puede ser alcanzado por cualquier m odulo en el programa. El m odulo asm io declara las etiquetas print int etc., globales . Este es el porque uno puede usarlas en el m odulo first.asm.

1.4.2.

Dependencias del compilador

El c odigo de ensamblador de arriba es espec co del compilador libre GNU C/C++8 DJGPP .9 Este compilador puede ser descargado libremente de internet. Requiere un PC 386 o posterior y se ejecuta bajo Dos, Windows 95/98 o NT. Este compilador usa archivos objeto con formato COFF (common objet le format). Para ensamblar este formato use la opci on -f coff con nasm (como se muestra en los comentarios del c odigo). La extensi on del archivo objeto resultante ser a o. El compilador de C de linux tambi en es GNU. Para convertir el c odigo para que corra bajo linux simplemente quita los gui on bajos de prejos en las l neas 37 y38. Linux usa el formato ELF (Excecutable an Linkable Format) para los archivos objetos. Use la opci on -f elf para linux. Tambi en produce un objeto con una extensi on o. Borland C/C++ es otro compilador popular. Usa el formato de OMF de microsoft para los archivos objeto. Use la opci on -f obj para los compiladores de Borland. La extensi on del archivo objeto ser a obj. El formato OMF utiliza unas directivas de segmento diferentes que los otros formatos de objetos. El segmento data (l nea 13) se debe cambiar a: 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 Adem as se debe a nadir una nueva l nea antes de la l nea 36. 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, el convierte la informaci on internamente en win 32). El formato win 32 permite que los segmentos se denan tal como DJGPP y linux. Use la opci on -f win 32 para este formato. La extensi on del archivo objeto ser a obj.
8 9

Los archivos de un compilador dado est an disponibles en la p agina web del autor ya modicados para que trabajen con el compilador apropiado.

GNU es un proyecto de la Free Software Foundation (http://www.fsf.org) http://www.deloire.com/djgpp

24

CAP ITULO 1. INTRODUCCION

1.4.3.

Ensamblando el c odigo

El primer paso es ensamblar el c odigo. Desde la l nea de orden digite ; nasm -f formato-de-objeto first.asm Donde el formato del objeto es co ,elf , obj o win 32 dependiendo que compilador de C ser a usado. (Recuerde que tambi en se deben cambiar los archivos fuente por linux y Borland tambi en).

1.4.4.

Compilando el c odigo de C

Compile el archivo driver.c usando un compilador de C. Para DJGPP, digite: gcc -c driver.c La opci on -c signica que solo compile, no intente encadenar a un. Esta misma opci on trabaja en los compiladores de Linux, Borland, y Microsoft tambi en.

1.4.5.

encadenando los archivos objeto

El encadenamiento es un proceso de combinar el c odigo de m aquina y los datos en archivos objeto con archivos de biblioteca para crear un archivo ejecutable. Como se ver a adelante, este proceso es complicado. El c odigo de C requieren la biblioteca estandar de C y un c odigo de inicio especial para ejecutarse. Es mucho m as f acil dejar que el compilador de C llame al encadenador con los par ametros correctos que intentar llamar al encadenador directamente. Por ejemplo encadenar el c odigo para el primer programa utilizando DJGPP, digite: gcc -o first driver.o first.o asm io.o Esto crea un ejecutable llamado first.exe (o solo first bajo Linux). Con Borland uno usar a: bcc32 first.obj driver.obj asm io.obj Borland usa el nombre del primer archivo en la lista para determinar el nombre del ejecutable. As en el caso anterior el programa deber a llamarse first.exe. Es posible combinar el paso de compilar y encadenar. Por ejemplo: gcc -o first driver.c first.o asm io.o Ahora gcc compilar a driver.C y entonces lo encadenar a.

1.4. CREANDO UN PROGRAMA

25

1.4.6.

Entender un archivo de listado de ensamblador

La opci on -l archivo-de-listado se puede usar para decirle a nasm que cree un archivo de listado con un nombre dado. Este archivo muestra c omo se ensambl o el c odigo. Se muestra c omo las l neas 17 y 18 (en el segmento data) aparecen en el archivo de listado. Los n umeros de las l neas est an en el archivo de listado; sin embargo observe que los n umeros de las l neas en el archivo fuente pueden no ser los mismas que las del archivo de listado. 48 49 50 51 52 00000000 00000009 00000011 0000001A 00000023 456E7465722061206E756D6265723A2000 456E74657220616E6F74686572206E756D6265723A2000 prompt1 db prompt2 db "Enter a number: ", 0 "Enter another number: ", 0

La primera columna en cada l nea es el n umero 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 c odigos ASCII. Finalmente en la l nea se muestra el texto del c odigo fuente. Los desplazamientos mostrados en la segunda columna son muy probables que no sean los desplazamientos reales, los datos ser an colocados en el programa completo. Cada m odulo puede denir sus propias etiquetas en el segmento de datos ( y en los otros segmentos tambi en). En el paso de encadenamientoi vea la Secci on 1.4.5 , todas estas deniciones de segmentos y etiquetas son combinadas para formar un solo segmento de datos. El encadenador entonces calcula el desplazamiento denitivo. Se muestra una peque na secci on (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 add mov eax, [input1] eax, [input2] ebx, eax

La tercera columna muestra el c odigo de m aquina generado por el ensamblador. A menudo el c odigo completo de una instrucci on no se puede calcular a un. Por ejemplo, en la l nea 94 el desplazamiento (o direcci on) de input1 no se conoce hasta que el c odigo se encadene. El ensamblador puede calcular el c odigo de la instrucci on mov (que del listado es A1), pero escribe el desplazamiento en par entesis cuadrados porque el valor exacto no se puede calcular en este momento. En este caso se utiliza un desplazamiento temporal de 0 porque input1 est a al inicio de la parte del segmento bss denido en este archivo. Recuerde que esto no signica que estar a al comienzo del segmento bss denitivo del programa. Cuando el c odigo es encadenado, el

26

CAP ITULO 1. INTRODUCCION

encadenador insertar a el desplazamiento en la posici on correcta. Otras instrucciones como la l nea 96 no hacen referencia a ninguna etiqueta. Ac a el ensamblador puede calcular el c odigo de m aquina completo. Representaciones Big y Little Endian Si uno mira de cerca en la l nea 95 hay algo muy extra no sobre el desplazamiento en los par entesis cuadrados del c odigo de m aquina. La etiqueta input2 tiene un desplazamiento de 4 (como est a denido en este archivo); sin embargo, el desplazamiento que aparece en la memoria no es 0000004, pero 04000000. Por qu e? Diferentes procesadores almacenan enteros de varios bytes en ordenes diferentes en la memoria. Existen dos m etodos populares Endian se pronuncia como de almacenar enteros: big endian y littel endian. Big endian es el m etodo que indian. se ve m as natural. El byte mayor (m as signicativo ) 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 m etodo de Big endian. Sin embargo los procesadores Intel us an el m etodo little endian. Ac a se almacena primero el byte menos signicativo. As 00000004 se almacenar a en memoria como 04 00 00 00. Este formato est a cableado en la CPU y no se puede cambiar. Normalmente el programador no necesita preocuparse sobre que formato est a usando. Sin embargo hay circunstancias donde esto es importante. 1. Cuando un dato binario es transere entre computadores diferentes (o de archivos o a trav es de una red) 2. 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 Lo Endian no se aplica al orden de los elementos de un arreglo. El primer elemento de un arreglo est a siempre en la direcci on menor. Esto se aplica a cadenas (que s olo son arreglos de caracteres). Lo Endian s olo 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

1.5. ARCHIVO ESQUELETO

27

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25

skel.asm %include "asm_io.inc" segment .data ; ; los datos iniciados se colocan en el segmento de ; datos ac a ; segment .bss ; ; Datos no iniciados se colocan en el segmento bss ; segment .text global _asm_main _asm_main: enter 0,0 ; rutina de pusha ; ; El c odigo est a colocado en el segmento de texto. No modifique el ; c odigo antes o despu es de este comentario ; popa mov eax, 0 ; retornar a C leave ret skel.asm Figura 1.7: Programa esqueleto

28

CAP ITULO 1. INTRODUCCION

Cap tulo 2

Lenguaje ensamblador b asico


2.1.
2.1.1.

Trabajando con enteros


Representaci on de enteros

Hay dos tipos de enteros: sin signo y con signo. Los enteros sin signo (que son no negativos) est an representados de una manera muy sencilla en binario. El n umero 200 como un byte entero sin signo ser a representado como 11001000 (o C8 en hex). Los enteros con signo (que pueden ser positivos o negativos) se representan de modo m as complejo. Por ejemplo considere 56. +56 como byte ser a representado por 00111000. En el papel uno podr a representar 56 como 111000, pero c omo podr a representarse en un byte en la memoria del computador? C omo se almacenar a el signo? Hay 3 t ecnicas que se han usado para representar n umeros enteros en la memoria del computador. Todos estos m etodos usan el bit m as signicativo como un bit de signo. Este bit es 0 si el n umero es positivo y 1 si es negativo. Magnitud y signo El primer m etodo es el m as elemental y es llamado magnitud y signo. Representa los enteros como dos partes. 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 est a subrayado) y 56 ser a 10111000. El mayor valor de un byte ser a 01111111 o +127 y el menor valor ser a 11111111 o 127. Para negar un valor se cambia el bit del signo. Este m etodo es sencillo, pero tiene sus inconvenientes. Primero hay dos valores posibles de cero +0 (00000000) y 0 (10000000). Ya que cero no es ni positivo ni negativo, las dos representaciones podr an servir igual. Esto complica la l ogica de la aritm etica para la CPU. Segundo, la aritm etica general tambi en es complicada. Si se 29

30

CAP ITULO 2. LENGUAJE ENSAMBLADOR BASICO

a nade 10 a 56, este debe transformarse en la resta de 10 y 56. Una vez m as, esto complica la l ogica de la CPU. Complemento a uno El segundo m etodo es conocido como complemento a uno. El complemento a uno de un n umero se encuentra invirtiendo cada bit en el n umero. (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. En la notaci on de complemento a uno calcular el complemento a uno es equivalente a la negaci on. As 11000111 es la representaci on de 56. Observe que el bit de signo fue cambiado autom aticamente por el complemento a uno y que como se esperar a al aplicar el complemento a 1 dos veces produce el n umero original. Como el primer m etodo hay dos representaciones del cero etica con n umeros en complemento 00000000 (+0) y 11111111 (0). La aritm a uno es complicada. Hay un truco u til para encontrar el complemento a 1 de un n umero en hexadecimal sin convertirlo a binario. El truco es restar el n umero hexadecimal de F (o 15 en decimal) . Este m etodo supone que el n umero de d gitos binarios en el n umero es un m ultiplo de 4. Un ejemplo: +56 se representa por 38 en hex. Para encontrar el complemento a 1 reste F de cada d gito para obtener C7 en hexadecimal. Esto es coherente con el resultado anterior. Complemento a dos Los dos primeros m etodos descritos fueron usados en los primeros computadores. Los computadores modernos usan un tercer m etodo llamado la representaci on en complemento a 2. El complemento a 2 de un n umero se halla con los dos pasos siguientes: 1. Hallar el complemento a uno del n umero. 2. Sumar uno al resultado del paso 1. Ac a est a un ejemplo usando 00111000 (56). Primero se calcula el complemento a uno: 11000111. Entonces se a nade uno: 11000111 1 11001000

En la notaci on de complemento a dos, calcular el complemento a dos es equivalente a negar el n umero. As 11001000 es la representaci on en complemento a dos de 56. Dos negaciones deber an reproducir el n umero original.

2.1. TRABAJANDO CON ENTEROS Number 0 1 127 -128 -127 -2 -1 Hex Representation 00 01 7F 80 81 FE FF

31

Cuadro 2.1: Representaci on de complemento a dos Sorprendentemente el complemento a dos no re une este requisito. Tome el complemento a dos de 11001000 a nadiendo uno al complemento a uno. 00110111 + 1 00111000 Cuando realizamos la suma en una operaci on en complemento a dos, la suma del bit del extremo izquierdo puede producir un carry. Este carry no se usa. Recuerde que todos los datos en el computador son de un tama no jo (en t erminos de n umeros de bits). Sumar 2 bytes siempre produce como resultado un byte (tal como sumar 2 palabras produce otra palabra, etc.). Esta propiedad es importante para la notaci on en complemento a dos. Por ejemplo, considere el cero como un n umero en complemento a dos de un byte (00000000). Calcular el complemento a 2 produce la suma: 11111111 + 1 c 00000000 donde c representa un carry (luego se mostrar a como detectar este carry, pero no se almacena en el resultado). As en la notaci on de complemento a dos existe solo un cero. Esto hace la aritm etica de complemento a dos m as simple que los m etodos anteriores. Usando la notaci on en complemento a dos un byte con signo se puede usar para representar los n umeros desde 128 hasta +127. La Tabla 2.1 muestra algunos valores seleccionados. Si se usan 16 bits, se pueden representar los n umeros con signo desde 32,768 hasta 32,767 que est a representado por 7FFF, 32,768 por 8000, 128 como FF80 y -1 como FFFF. Los n umeros de 32 bits en complemento a dos est an en el rango de 2 mil millones a +2 mil millones aproximadamente. La CPU no tiene ni idea que supuesta representaci on tiene un byte en particular (palabra, o palabra doble). El ensamblador no tiene ni idea de los

32

CAP ITULO 2. LENGUAJE ENSAMBLADOR BASICO

tipos de datos que tienen los lenguajes de alto nivel. C omo se interpretan los datos depende de qu e instrucci on se usa con el dato. Si el valor FF es considerado para representar 1 o +255 depende del programador. El lenguaje C dene tipos de entero con y sin signo (Signed, unisigned). Esto le permite al compilador determinar las instrucciones correctas a usar con el dato.

2.1.2.

Extensi on del signo

En ensamblador, todos los datos tienen un tama no determinado. No es raro necesitar cambiar el tama no del dato para usarlo con otro dato. Reducir el tama no es f acil. Reduciendo el tama no de los datos Para reducir el tama no del dato simplemente quite los bits m as signicativos del dato. Un ejemplo trivial: mov mov ax, 0034h cl, al ; ax = 52 (almacenado en16 bits) ; cl = los 8-bits inferiores de ax

Claro est a, si el n umero no se puede representar correctamente en el tama no m as peque no la reducci on de tama no no funcionar a. Por ejemplo si AX era 0134h (o 308 en decimal) entonces el c odigo anterior almacenar a en CL 34h. Este m etodo trabaja con n umeros con o sin signo. Considere n umeros con signo, si AX era FFFFh (-1 como palabra), entonces code CL ser a FFh (1 como byte). Sin embargo, observe que esto no es correcto si el valor en AX era sin signo! La regla para n umeros sin signo es que todos los bits al ser quitados deben ser 0 para que la conversi on sea correcta. La regla para los n umeros con signo es que los bits que sean quitados deben ser o todos 1o todos 0. Adem as el primer bit no se debe quitar pero debe tener el mismo valor que los bits quitados. Este bit ser a el nuevo bit de signos del valor m as peque no. Es importante que sea el bit del signo original. Aumentando el tama no de los datos Incrementar el tama no de los datos es m as complicado que disminuirlo. Considere el byte hex FF. Si se extiende a una palabra, Qu e valor deber a tener la palabra? Depende de c omo se interprete la palabra. Si FF es un byte sin signo (255 en decimal), entonces la palabra deber a ser 00FF; sin embargo, si es un byte con signo (1 en decimal), entonces la palabra deber a ser FFFF.

2.1. TRABAJANDO CON ENTEROS

33

En general, para extender un n umero sin signo, uno hace cero todos los bits nuevos del n umero extendido. As FF se convierte en 00FF. Sin embargo, para extender un n umero con signo uno debe extender el bit de signo. Esto signica que los nuevos bits se convierten en copias del bit de signo. Ya que el bit de signo de FF es 1, los nuevos bits deben ser todos unos, para producir FFFF. Si el n umero con signo 5A (90 en decimal) fue extendido, el resultado ser a 005A. Existen varias instrucciones que suministra el 80386 para la extensi on de los n umeros. Recuerde que el computador no conoce si un n umero est a con o sin signo. Es responsabilidad del programador usar la instrucci on adecuada. Para n umeros sin signo, uno puede simplemente colocar ceros en los bits superiores usando una instrucci on MOV. Por ejemplo, para extender el byte en AL a una palabra sin signo en AX: mov ah, 0 ; cero los 8-bits superiores

Sin embargo, no es posible usar la instrucci on MOV para convertir la palabra sin signo en AX a una palabra doble en EAX. Por qu e no? No hay manera de referirse a los 16 bits superiores de EAX con una instrucci on MOV. El 80386 resuelve este problema con una nueva instrucci on MOVZX . Esta instrucci on tiene dos operandos. El destino (primer operando) debe ser un registro de 16 o 32 bits. La fuente (segundo operando) puede ser un registro de 8 o 16 bits o un byte o una palabra en memoria. La otra restricci on 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 no). Algunos ejemplos: movzx movzx movzx movzx eax, ax eax, al ax, al ebx, ax ; ; ; ; extiende extiende extiende extiende ax al al ax en en en en eax eax ax ebx

Para n umeros con signo, no hay una manera f acil de usar la instrucci on MOV. EL 8086 suministra varias instrucciones para extender n umeros con signo. La instrucci on CBW (Convert Byte to Word) extiende el registro AL en AX. Los operandos son impl citos. La instrucci on CWD (Convert Word to Double Word) extiende AX en DX:AX. La notaci on DX:AX signica 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. (Recuerde que el 8086 no ten a ning un registro de 32 bits). El 80386 a nadi o varias instrucciones nuevas. La instrucci on CWDE (Convert Word to Double word Extended) extiende AX en EAX. La instrucci on CDQ (Convert Double word to Quad word) extiende EAX en EDX:EAX (64 bits!). Finalmente, la instrucci on MOVSX trabaja como MOVZX excepto que usa las reglas para n umeros con signo.

34

CAP ITULO 2. LENGUAJE ENSAMBLADOR BASICO

unsigned char uchar = 0xFF; signed char schar = 0xFF; int a = ( int ) uchar ; / a = 255 (0x000000FF) / int b = (int ) schar ; / b = 1 (0xFFFFFFFF) / Figura 2.1: char ch; while ( (ch = fgetc(fp )) != EOF ) { / hace algo con ch / } Figura 2.2:

Aplicaci on a la programaci on en C
ANSI C no dene si el tipo char es con signo o no, es responsabilidad de cada compilador decidir esto. Esta es la raz on por la cual el tipo est a expl citamente denido en la Figura 2.1.

Extender enteros con y sin signo tambi en ocurre en C. Las variables en C se pueden declarar como int signed o unsigned (int es signed) . Considere el c odigo de la Figura 2.1. En la l nea 3, la variable code a se extiendo usando las reglas para valores sin signo (usando MOVZX), pero en la l nea 4 se usan las reglas con signo para b (usando MOVSX). Hay un error muy com un en la programaci on en C que tiene que ver con esto directamente. Considere el c odigo de la Figura 2.2. El prototipo de fgetc( ) es: fgetc()is: int fgetc( FILE * ); Uno podr a preguntar Por qu e la funci on retorna un int siendo que lee caracteres? La raz on es que normalmente retorna un char (extendido a un valor entero usando la extensi on cero). Sin embargo hay un valor que puede retornar que no es un car acter, EOF. Este es un macro que normalmente se dene como 1. As fgetc() o retorna un car acter extendido a entero (que es como 000000xx en hex) o EOF (que es FFFFFFF en hex). El problema principal con el programa de la Figura 2.2 es que fgetc() retorna un entero, pero este valor se almacena en un char. C truncar a los bits de mayor peso para que el entero quepa en el caracter. El u nico problema es que los n umeros (en hex) 000000FF y FFFFFFFF ambos se truncar an al byte FF. As el ciclo while no puede distinguir entre el byte FF y el n de archivo (EOF).

2.1. TRABAJANDO CON ENTEROS

35

Lo que sucede exactamente en este caso, depende de si el char es con signo o sin signo por qu e? Porque en la l nea 2 ch es comparada con EOF. Ya que EOF es un valor int. 1 ch ser a extendido a un int de modo que los dos valores comparados sean del mismo tama no. 2 como se muestra en la Figura 2.1, donde si la variable es con signo o sin signo es muy importante. Si char es unsigned, FF se extender a a 000000FF. Esto se compara con EOF (FFFFFFFF) y encontrar a que no es igual. As , el bucle nunca terminar a!. Si char es con signo se extender a a FFFFFFFF. Esto se compara como igual y el bucle naliza. Sin embargo, ya que el byte FF puede haber sido le do de un archivo, el bucle podr a terminar prematuramente. La soluci on a este problema es denir la variable ch como un int no como un char. Cuando esto se hace no se truncar a o extender a en la l nea 2. Dentro del bucle es seguro truncar el valor ya que ah ch debe ser un simple byte.

2.1.3.

Aritm etica de complemento a dos

Como se vio al principio, la instrucci on add efect ua una suma y la instrucci on sub efect ua una resta. Dos de los bits en el registro FLAGS, que estas instrucciones jan son las banderas de desborde y carry. La bandera de desborde se ja si el resultado verdadero de la operaci on es muy grande para caber en el destino para aritm etica con signo. La bandera de carry se ja si hay carry en el bit m as signicativo de una suma o un pr estamo en el bit m as signicativo de una resta. As , el se puede usar para detectar un desborde para aritm etica sin signo. Los usos de la bandera de carry para aritm etica con signo se ver an dentro de poco. Una de las grandes ventajas del complemento a 2 es que las reglas para la adici on y sustracci on son exactamente las mismas que para los enteros sin signo. As add y sub se pueden usar con enteros con o sin signo. 002C + FFFF 002B 44 + (1) 43

Hay un carry generado, pero no es parte de la respuesta.


Es un concepto err oneo pensar que los archivos tienen un car acter EOF al nal. Esto no es verdad! 2 La raz on para este requerimiento se mostrar a luego.
1

36 dest

CAP ITULO 2. LENGUAJE ENSAMBLADOR BASICO source1 reg/mem8 reg/mem16 reg/mem32 reg/mem16 reg/mem32 immed8 immed8 immed16 immed32 reg/mem16 reg/mem32 reg/mem16 reg/mem32 source2 Action AX = AL*source1 DX:AX = AX*source1 EDX:EAX = EAX*source1 dest *= source1 dest *= source1 dest *= immed8 dest *= immed8 dest *= immed16 dest *= immed32 dest = source1*source2 dest = source1*source2 dest = source1*source2 dest = source1*source2

reg16 reg32 reg16 reg32 reg16 reg32 reg16 reg32 reg16 reg32

immed8 immed8 immed16 immed32

Cuadro 2.2: instrucciones imul Hay dos instrucciones diferentes para multiplicar y dividir. Primero para multiplicar use la instrucci on MUL o IMUL. La instrucci on MUL se emplea para multiplicar n umeros sin signo e IMUL se usa para multiplicar enteros con signo. Por qu e se necesitan dos instrucciones diferentes? Las reglas para la multiplicaci on son diferentes para n umeros en complemento a dos con signo o sin signo. C omo as ? Considere la multiplicaci on del byte FF con s mismo dando como resultado una palabra. Usando una multiplicaci on sin signo es 255 veces 255 o 65025 (o FE01 en hex). Usando la multiplicaci on con signo es 1 veces 1 (o 0001 en hex). Hay varias formas de las instrucciones de multiplicaci on. La m as antigua es: mul source

source es un registro o una referencia a memoria. No puede ser un valor inmediato. Exactamente qu e multiplicaci on se realiza depende del tama no 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 instrucci on IMUL tiene la misma forma de MUL, pero tambi en tiene algunos otros formatos. Hay dos y tres formas de operandos. imul imul dest, source1 dest, source1, source2

La Tabla 2.2 muestra las posibles combinaciones.

2.1. TRABAJANDO CON ENTEROS

37

Los dos operadores para la divisi on son DIV e IDIV. Ellas efect uan la divisi on 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 instrucci on 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 un es olvidar iniciar DX o EDX antes de la divisi on. La instrucci on 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

38
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49

CAP ITULO 2. LENGUAJE ENSAMBLADOR BASICO

%include "asm_io.inc" segment .data prompt db square_msg db cube_msg db cube25_msg db quot_msg db rem_msg db neg_msg db segment .bss input resd 1 segment .text global _asm_main: enter pusha mov call call mov imul mov mov call mov call call mov imul mov call mov call call imul mov call mov call call mov

; Cadenas de salida "Digite un n umero: ", 0 "La entrada al cuadrado es ", 0 "La entrada al cubo es ", 0 "La entrada al cubo 25 veces es ", 0 "El cociente del cubo/100 es ", 0 "El residuo del cube/100 es ", 0 "La negaci on del residuo es ", 0

_asm_main 0,0 ; rutina de inicio

eax, prompt print_string read_int [input], eax eax 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 print_nl eax, ebx ; edx:eax = eax * eax ; guarda la respuesta en

ebx

; ebx *= [input]

; ecx = ebx*25

2.2. ESTRUCTURAS DE CONTROL math.asm

39

2.1.5.

Aritm etica de precisi on extendida

El lenguaje ensamblador tambi en suministra instrucciones que le permitan a uno hacer operaciones de suma y resta de n umeros m as grandes que palabras dobles. Como se vio antes las instrucciones ADD y SUB modican la bandera de carry si se ha generado un carry o un pr estamo respectivamente. Esta informaci on almacenada en la bandera de carry se puede usar para sumar o restar n umeros grandes dividiendo la operaci on en piezas peque nas de palabras dobles (o menores). Las instrucciones ADC y SBB usan esta informaci on en la bandera de carry. La instrucci on ADC hace la siguiente operaci on: operando1 = operando1 + bandera de carry + operando2 La instrucci on SBB realiza: operando1 = operando1 - bandera de flag - operando2 C omo se usan? Considere la suma de enteros de 64 bits en EDX:EAX y EBX:ECX . El siguiente c odigo pod a almacenar la suma en EDX:EAX
1 2

add adc sub sbb

eax, ecx edx, ebx eax, ecx edx, ebx

; suma los 32-bits inferiores ; suma los 32-bits superiores y el carry de la suma anterior ; resta los 32-bits inferiores ; resta los 32-bits superiores y el pr estamo

La resta es muy similar. El siguiente c odigo resta EBX:ECX de EDX:EAX


1 2

Para n umeros realmente grandes, se puede usar un bucle (vea secci on 2.2). Para el bucle suma ser a conveniente usar la instrucci on ADC para cada iteraci on (en todas menos la primera). Esto se puede hacer usando la instrucci on CLC (CLear Carry) antes que comience el bucle para iniciar la bandera de carry a cero. Si la bandera de carry es cero no hay diferencia entre ADD y ADC. La misma idea se puede usar tambi en para la resta.

2.2.

Estructuras de control

Los lenguajes de alto nivel suministran estructuras del alto nivel (instrucciones if while ), que controlan el ujo de ejecuci on. El lenguaje ensamblador no suministra estas complejas estructuras de control. En lugar de ello usa el difamado goto y su uso inapropiado puede resultar en un c odigo spaghetTi! Sin embargo es posible escribir programas estructurados en ensamblador. El procedimiento b asico es dise nar la l ogica del programa usando las estructuras de control de alto nivel y traducir el dise no en lenguaje ensamblador apropiado (parecido a lo que har a el compilador).

40

CAP ITULO 2. LENGUAJE ENSAMBLADOR BASICO

2.2.1.

Comparaciones

Las estructuras de control deciden que hacer basados en la comparaci on de datos. En ensamblador, el resultado de una comparaci on se almacenan en el registro FLAGS para usarlas luego. El 80x86 suministra la instrucci on CMP para realizar comparaciones. El registro FLAGS se ja basado en la diferencia de los dos operandos de la instrucci on CMP. Los operandos se restan y se ja el registro FLAGS basado en el resultado, pero el resultado no se almacena en ninguna parte. Si necesita el resultado use la instrucci on SUB en lugar de la instrucci on CMP. Para enteros sin signos hay dos banderas (bits en el registro FLAGS) que son importante: cero (ZF) y carry (CF). La bandera cero se ja si el resultado de la resta es cero. La bandera carry se usa como bandera de pr estamo para la resta. Considere una comparaci on como: cmp vleft, vright

Por qu e hace SF = OF si vleft > vright? Si no hay desborde, entonces la diferencia tendr a el valor correcto y debe ser no negativo. As , SF = OF = 0. Sin embargo, si hay un desborde, la diferencia no tendr a el valor correcto (y de hecho ser a negativo). As SF = OF = 1.

La resta de vleft - vright se calcula y las banderas se jan de acuerdo al resultado. Si la diferencia de CMP es cero, vleft = vright, entonces ZF se ja (ZF=1) y CF se borra (CF=0). Si vleft > vright, entonces ZF se borra y CF tambi en (no hay pr estamo). Si vleft < vright, entonces ZF se borrar a y CF se jar a (hay pr estamo). Para enteros con signo, hay tres banderas que son importante: la bandera cero (ZF), la bandera de desborde (OF) y la bandera de signo (SF). La bandera de desborde se ja si el resultado de una operaci on se desborda (o underows). La bandera de signo se ja si el resultado de una operaci on es negativo. Si vleft = vright, la ZF se ja ( tal como para los enteros sin signo). Si vleft > vright, ZF es cero y SF = OF. Si vleft < vright, ZF es cero y SF = OF. No olvide que otras instrucciones tambi en cambian el registro FLAGS, no solo CMP.

2.2.2.

Instrucciones de ramicaci on

Las instrucciones de ramicaci on pueden transferir la ejecuci on de un programa a un punto arbitrario. En otras palabras funcionan como goto. Hay dos tipos de ramicaciones: condicionales e incondicionales. Una ramicaci on incondicional es tal cual como goto, siempre hace el salto. Una ramicaci on condicional puede o no hacer el salto dependiendo de las banderas del registro FLAGS. Si una ramicaci on condicional no hace el salto, el control pasa a la siguiente instrucci on. La instrucci on JMP (acr onimo de jump ) hace ramicaciones incondicionales. Su argumento normalmente es una etiqueta de c odigo a la instrucci on a la cual se debe saltar. El ensamblador o el encadenador reemplazar a la

2.2. ESTRUCTURAS DE CONTROL

41

etiqueta con la direcci on correcta de la instrucci on. Esta es otra de las labores aburridas que realiza el ensamblador para hacer la vida del programador m as f acil. Es importante tener en cuenta que la instrucci on inmediatamente despu es de la instrucci on JMP nunca se ejecutar a a menos que otra instrucci on salte a ella. Hay variaciones de la instrucci on de salto. SHORT Este salto es de tama no muy limitado, solo se puede mover arriba o abajo 128 bytes en memoria. La ventaja de este tipo es que usa menos memoria que otros. Usa un byte con signo para almacenar el desplazamiento del salto. El desplazamiento es cu antos bytes se mueve adelante o atr as. (El desplazamiento se a nade a EIP). Para especicar un salto corto, use la palabra SHORT inmediatamente antes de la etiqueta en la instrucci on JMP. NEAR Este salto es el tipo por defecto en las ramicaciones condicionales e incondicionales y se puede usar para saltar a cualquier lugar del segmento. Actualmente el 80386 soporta 2 tipos de saltos cercanos. Uno usa dos bytes para el desplazamiento. Esto le permite a uno moverse aproximadamente 32000 bytes arriba o abajo. El otro tipo usa cuatro bytes para el desplazamiento, que le permite a uno moverse a cualquier lugar en el segmento de c odigo. El tipo de 4 bytes es el de defecto en el modo protegido del 386. El tipo de 2 bytes se puede especicar colocando la palabra WORD antes de la etiqueta en la instrucci on JMP. FAR Este salto permite mover el control a otro segmento de c odigo. Este es una cosa muy rara para hacerla en el modo protegido del 386. Las etiquetas de c odigo v alidas siguen las mismas reglas que las etiquetas de datos. Las etiquetas de c odigo est an denidas para colocarlas en el segmento de c odigo al frente de la instrucci on sus etiquetas. Dos puntos se colocan al nal de la etiqueta en este punto de la denici on. Los dos puntos no son parte del nombre. Hay muchas instrucciones de ramicaci on condicional diferentes. Ellas tambi en toman la etiqueta como su operando. Las m as sencillas solo ven una bandera en el registro FLAGS para determinar si salta o no. Vea la Tabla 2.3 para una lista de estas instrucciones (PF es la bandera de paridad que indica si el n umero de unos en los 8 bit inferiores es par o impar). El siguiente pseudoc odigo: if ( EAX == 0 ) EBX = 1; else EBX = 2;

42

CAP ITULO 2. LENGUAJE ENSAMBLADOR BASICO JZ JNZ JO JNO JS JNS JC JNC JP JNP salta salta salta salta salta salta salta salta salta salta solo solo solo solo solo solo solo solo solo solo si si si si si si si si si si ZF es uno ZF es cero OF es uno OF es cero SF es uno SF es cero CF es uno CF es cero PF es uno PF es cero

Cuadro 2.3: Saltos condicionales simples Se podr a escribir en ensamblador como:


1 2 3 4 5 6 7

cmp jz mov jmp thenblock: mov next:

eax, 0 thenblock ebx, 2 next ebx, 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

eax - 0 = 0)

; parte THEN del IF

Las otras comparaciones no son f aciles usando las ramicaciones condicionales de la Tabla 2.3 Para ilustrar esto considere el siguiente pseudoc odigo: if ( EAX >= 5 ) EBX = 1; else EBX = 2; Si EAX es mayor que o igual a 5, ZF debe estar ja o borrada y SF ser a igual a OF. Aqu est a el c odigo en ensamblador que prueba estas condiciones (asumiendo que EAX es con signo):
1 2 3 4 5 6 7 8

cmp js jo jmp signon: jo elseblock: mov

eax, 5 signon elseblock thenblock thenblock ebx, 2

; salta a signon si SF = 1 ; salta a elseblock si OF = 1 y SF = 0 ; salta a thenblock si SF = 0 y OF = 0 ; salta a thenblock si SF = 1 y OF = 1

2.2. ESTRUCTURAS DE CONTROL Signed salta si vleft salta si vleft salta si vleft salta si vleft salta si vleft salta si vleft Unsigned salta si vleft salta si vleft salta si vleft salta si vleft salta si vleft salta si vleft

43

JE JNE JL, JNGE JLE, JNG JG, JNLE JGE, JNL

= vright = vright <vright vright >vright vright

JE JNE JB, JNAE JBE, JNA JA, JNBE JAE, JNB

= vright = vright <vright vright >vright vright

Cuadro 2.4: Instrucciones de comparaci on con y sin signo


9 10 11 12

jmp thenblock: mov next:

next ebx, 1

El c odigo anterior es muy complicado. Afortunadamente, el 80x86 suministra otras instrucciones de ramicaci on que hace este tipo de pruebas mucho m as f acil. Hay versiones con y sin signo para cada tipo. La Tabla 2.4 muestra estas instrucciones. Las ramicaciones igual y no igual (JE y JNE) son id enticas para enteros con y sin signo. (De hecho JE y JNE son las mismas que JZ y JNZ respectivamente). Cada una de las otras instrucciones de ramicaci on tienen dos sin onimos. Por ejemplo mire que JL (jump less than) y JNGE (jump not greater than or equal to). Son la misma instrucci on porque: x < y = not(x y ) Las ramicaciones sin signo usan A por above y B para below en lugar de L y G. Usando estas nuevas instrucciones de ramicaci on, el pseudoc odigo de arriba puede ser traducido a ensamblador mucho m as f acil.
1 2 3 4 5 6 7

cmp jge mov jmp thenblock: mov next:

eax, 5 thenblock ebx, 2 next ebx, 1

2.2.3.

Instrucciones de bucle

El 80x86 suministra varias instrucciones para implementar bucles del tipo for. Cada una de esas instrucciones tiene una etiqueta como u nico operando.

44

CAP ITULO 2. LENGUAJE ENSAMBLADOR BASICO

LOOP Decrementa ECX, si ECX = 0, salta a la etiqueta LOOPE, LOOPZ Decrementa ECX (el registro FLAGS no se modica), si ECX = 0 y ZF = 1, salta LOOPNE, LOOPNZ Decrementa ECX (FLAGS sin cambio), si ECX = 0 y ZF = 0, salta Las dos u ltimas instrucciones de bucle son u tiles para bucles de b usqueda secuencial. El siguiente pseudoc odigo: sum = 0; for ( i =10; i >0; i ) sum += i; Podr a ser traducido a ensamblador como:
1 2 3 4 5

mov mov loop_start: add loop

eax, 0 ecx, 10 eax, ecx loop_start

; eax es sum ; ecx es i

2.3.

Traducir estructuras de control est andares

Esta secci on muestra c omo las estructuras de control est andares de los lenguajes de alto nivel se pueden implementar en lenguaje ensamblador.

2.3.1.

instrucciones if

El siguiente pseudoc odigo: if ( o condicin ) bloque entonces; else bloque else ; podr a implementarse como:
1 2 3 4 5 6 7

; code to set FLAGS jxx else_block ; selecciona xx tal que salta si la condici on es falsa ; c odigo para bloque entonces jmp endif else_block: ; c odigo para bloque else endif:

2.3. TRADUCIR ESTRUCTURAS DE CONTROL ESTANDARES

45

Si no hay else, entonces el else block ramicado puede ser reemplazado por una rama a endif. If there is no else, then the else block branch can be replaced by a branch to endif.
1 2 3 4

; code to set FLAGS jxx endif ; selecciona xx tal que salta si la condici on es falsa ; c odigo para el bloque entonces endif:

2.3.2.

bucles while

El bucle while se prueba al inicio del bucle: while ( o condicin ) { cuerpo del bucle ; } Esto podr a traducirse en:
1 2 3 4 5 6

while: ; c odigo que fija FLAGS basado en la condici on de jxx endwhile ; selecciona xx tal que salte si xx es falso ; body of loop jmp while endwhile:

2.3.3.

bucles do while

El bucle do while se prueba al nal del bucle: do { cuerpo del bucle ; } while ( o condicin ); Esto podr a ser traducido en:
1 2 3 4

do: ; cuerpo del bucle ; c odigo para fijar FLAGS basado en la condici on jxx do ; seleccionar xx tal que salte si es verdadero

46

CAP ITULO 2. LENGUAJE ENSAMBLADOR BASICO /

unsigned guess; / La conjetura actual para el primo unsigned factor ; / el posible factor / unsigned limit ; / encontrar primos hasta este valor / printf (Find primes up to : ); scanf( %u, &limit); printf (2\n); / trata los dos primeros primos / printf (3\n); / como caso especial / guess = 5; / conjetura inicial / while ( guess <= limit ) { / busca un factor / factor = 3; while ( factor factor < guess && guess % factor != 0 ) factor += 2; if ( guess % factor != 0 ) printf ( %d\n, guess); guess += 2; / o slo busca en los u nmeros impares / } Figura 2.3:

2.4.

Ejemplo: hallar n umeros primos

Esta secci on trata de un programa que encuentra n umeros primos. Recuerde que un n umero primo es divisible s olo por 1 y por s mismo. No hay f ormula para hacer esto. El m etodo b asico de este programa es encontrar los factores de todos los n umeros impares 3 bajo un l mite dado. Si no se puede encontrar un factor para un n umero impar, es primo La Figura 2.3 muestra el algoritmo b asico escrito en C. Ac a est a la versi on en ensamblador %include "asm_io.inc" segment .data Message db segment .bss Limit Guess
3

prime.asm "Halle primos hasta: ", 0

1 2 3 4 5 6 7 8

resd resd

1 1

; halle primos hasta este l mite ; la conjetura actual para el primo

2 es el u nico n umero par.

2.4. EJEMPLO: HALLAR NUMEROS PRIMOS


9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50

47

segment .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 je

_asm_main 0,0 ; rutina de inicio

eax, Message print_string read_int [Limit], eax eax, 2 print_int print_nl eax, 3 print_int print_nl dword [Guess], 5 eax,[Guess] eax, [Limit] end_while_limit ebx, 3 eax,ebx eax end_while_factor eax, [Guess] end_while_factor eax,[Guess] edx,0 ebx edx, 0 end_while_factor

; scanf("%u", & limit );

; printf("2\n");

; printf("3\n");

; Guess = 5; ; while ( Guess <= Limit )

; use jnbe ya que los n umeros son sin signo ; ebx is factor = 3;

; edx:eax = eax*eax ; Si la respuesta no cabe en eax ; if !(factor*factor < guess)

; edx = edx:eax % ebx ; if !(guess % factor != 0) ; factor += 2;

add ebx,2 jmp while_factor end_while_factor: je end_if mov eax,[Guess]

; if !(guess % factor != 0) ; printf("%u\n")

48
51 52 53 54 55 56 57 58 59 60 61

CAP ITULO 2. LENGUAJE ENSAMBLADOR BASICO call call print_int print_nl ; guess += 2

end_if: add dword [Guess], 2 jmp while_limit end_while_limit: popa mov leave ret

eax, 0

; retorna a C prime.asm

Cap tulo 3

Operaciones con bits


3.1. Operaciones de desplazamientos

El lenguaje ensamblador le permite al programador manipular bits individuales de los datos. Una operaci on com un es llamada un desplazamiento. Una operaci on de desplazamiento mueve la posici on de los bits de alg un dato. Los desplazamientos pueden ser hacia la izquierda (hacia el bit m as signicativo) o hacia la derecha (el bit menos signicativo).

3.1.1.

Desplazamientos l ogicos

Un desplazamiento l ogico es el tipo m as simple de desplazamiento. Desplaza de una manera muy directa. La Figura 3.1 muestra un ejemplo del desplazamiento de un byte. Original Left shifted Right shifted 1 1 0 1 1 1 1 0 1 0 1 1 1 0 0 0 1 1 1 0 0 0 0 1

Figura 3.1: Desplazamientos l ogicos Observe que los nuevos bits son siempre cero. Se usan las instrucciones SHL y SHR para realizar los desplazamientos a la izquierda y derecha respectivamente. Estas instrucciones permiten desplazar cualquier n umero de posiciones. El n umero de posiciones puede ser o una constante o puede estar almacenado en el registro CL. El u ltimo bit desplazado se almacena en la bandera de carry. A continuaci on, algunos ejemplos:
1 2 3 4

mov shl shr shr

ax, ax, ax, ax,

0C123H 1 1 1

; desplaza un bit a la izquierda, ax = 8246H, CF = 1 ; desplaza un bit a la derecha, ax = 4123H, CF = 0 ; desplaza un bit a la derecha, ax = 2091H, CF = 1 49

50
5 6 7 8

CAP ITULO 3. OPERACIONES CON BITS ax, ax, cl, ax, 0C123H 2 3 cl

mov shl mov shr

; desplaza dos bit a la izquierda,

ax = 048CH, CF = 1

; desplaza tres bit a la derecha, ax = 0091H, CF = 1

3.1.2.

Uso de los desplazamientos

Los usos m as comunes de las operaciones de desplazamientos son las multiplicaciones y divisiones r apidas. Recuerde que en el sistema decimal la multiplicaci on y divisi on por una potencia de 10 es sencilla, s olo se desplazan los d gitos. Lo mismo se aplica para las potencias de dos en binario. Por ejemplo para duplicar el n umero binario 10112 (o 11 en decimal), al desplazar una vez a la izquierda obtenemos 101102 (o 22). El cociente de una divisi on por una potencia de dos es el resultado de un desplazamiento a la derecha. Para dividir por 2 solo haga un desplazamiento a la derecha; para dividir por 4 (22 ) desplace los bits 2 lugares; para dividir por 8 desplace 3 lugares a la derecha etc. Las instrucciones de desplazamiento son muy elementales y son mucho m as r apidas que las instrucciones correspondientes MUL y DIV . Actualmente, los desplazamientos l ogicos se pueden usar para multiplicar y dividir valores sin signo. Ellos no funcionan para valores con signo. Considere el valor bytes FFFF (1). Si este se desplaza l ogicamente a la derecha una vez el resultado es 7FFF que es +32,767. Se pueden usar otro tipo de desplazamientos para valores con signo.

3.1.3.

Desplazamientos aritm eticos

Estos desplazamientos est an dise nados para permitir que n umeros con signo se puedan multiplicar y dividir r apidamente por potencias de 2. Ellos aseguran que el bit de signo se trate correctamente. SAL (Shift aritmetic left). Esta instrucci on es solo sin onimo para SHL. Se ensambla con el mismo c odigo de m aquina que SHL. Como el bit de signo no se cambia por el desplazamiento, el resultado ser a correcto. SAR SAR Shift Arithmetic Right - Esta es una instrucci on nueva que no desplaza el bit de signo (el bit m as signicativo) de su operando. 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, si el bit de signo es 1, los nuevos bits son tambi en 1). As , si un byte se desplaza con esta instrucci on, s olo los 7 bits inferiores se desplazan. Como las otras instrucciones de desplazamiento, el u ltimo bit que sale se almacena en la bandera de carry.

3.1. OPERACIONES DE DESPLAZAMIENTOS


1 2 3 4

51

mov sal sal sar

ax, ax, ax, ax,

0C123H 1 1 2

; ax = 8246H, CF = 1 ; ax = 048CH, CF = 1 ; ax = 0123H, CF = 0

3.1.4.

Desplazamientos de rotaci on

Los desplazamientos de rotaci on trabajan como los desplazamientos l ogicos excepto que los bits perdidos en un extremo del dato se desplazan al otro lado. As , el dato es tratado como si fuera una estructura circular. Las dos rotaciones m as simples son ROL y ROR , que hacen rotaciones a la izquierda y a la derecha respectivamente. Tal como los otros desplazamientos, estos desplazamientos dejan una copia del u ltimo bit rotado en la bandera de carry.
1 2 3 4 5 6

mov rol rol rol ror ror

ax, ax, ax, ax, ax, ax,

0C123H 1 1 1 2 1

; ; ; ; ;

ax ax ax ax ax

= = = = =

8247H, 048FH, 091EH, 8247H, C123H,

CF CF CF CF CF

= = = = =

1 1 0 1 1

Hay dos instrucciones de rotaci on adicionales que desplazan los bits en el dato y la bandera de carry llamadas RCL y RCR . Por ejemplo, si el registro AX rota con estas instrucciones, los 17 bits se desplazan y la bandera de carry se rota.
1 2 3 4 5 6 7

mov clc rcl rcl rcl rcr rcr

ax, 0C123H ax, ax, ax, ax, ax, 1 1 1 2 1 ; ; ; ; ; ; borra la bandera ax = 8246H, CF = ax = 048DH, CF = ax = 091BH, CF = ax = 8246H, CF = ax = C123H, CF = de carry (CF = 0) 1 1 0 1 0

3.1.5.

Aplicaci on simple

A continuaci on est a un fragmento de c odigo que cuenta el n umero de bits que est an encendidos (1) en el registro EAX.
1 2

mov mov

bl, 0 ecx, 32

; bl contendr a el n umero de bits prendidos ; ecx es el bucle contador

52 X 0 0 1 1

CAP ITULO 3. OPERACIONES CON BITS Y 0 1 0 1 X AND Y 0 0 0 1

Cuadro 3.1: La operaci on AND 1 1 1 0 1 0 1 0 0 0 0 0 1 1 1 0 0 0 1 0 0 0 1 0

AND

Figura 3.2: ANDdo un byte


3 4 5 6 7 8

count_loop: shl jnc inc skip_inc: loop

eax, 1 skip_inc bl count_loop

; desplaza los bits en la bandera de carry ; si CF == 0, va a skip_inc

El c odigo anterior destruye el valor original de EAX (EAX es cero al nal del bucle). Si uno desea conservar el valor de EAX, la l nea 4 deber a ser reemplazada con rol eax,1.

3.2.

Operaciones booleanas entre bits

Hay cuatro operadores booleanos b asicos AND, OR, XOR y NOT. Una tabla de verdad muestra el resultado de cada operaci on por cada posible valor de los operandos.

3.2.1.

La operaci on AND

El resultado del AND de dos bits es 1 solo si ambos bits son 1, si no el resultado es cero como muestra la Tabla 3.1 Los procesadores tienen estas operaciones como instrucciones que act uan independientemente en todos los bits del dato en paralelo. Por ejemplo, si el contenido de AL y BL se les opera con AND, la operaci on se aplica a cada uno de los 8 pares de bits correspondientes en los dos registros como muestra la Figura 3.2 . Sigue un c odigo de ejemplo:
1 2

mov and

ax, 0C123H ax, 82F6H

; ax = 8022H

3.2. OPERACIONES BOOLEANAS ENTRE BITS X 0 0 1 1 Y 0 1 0 1 X OR Y 0 1 1 1

53

Cuadro 3.2: La operaci on OR X 0 0 1 1 Y 0 1 0 1 X XOR Y 0 1 1 0

Cuadro 3.3: La operaci on XOR

3.2.2.

La operaci on OR

El O inclusivo entre dos bits es 0 solo si ambos bits son 0, si no el resultado es 1 como se muestra en la Tabla 3.2 . Sigue un c odigo de ejemplo:
1 2

mov or

ax, 0C123H ax, 0E831H

; ax = E933H

3.2.3.

La operaci on XOR

El O exclusivo entre 2 bits es 0 si y solo si ambos bits son iguales, sino el resultado es 1 como muestra la tabla 3.3. Sigue un c odigo de ejemplo:
1 2

mov xor

ax, 0C123H ax, 0E831H

; ax = 2912H

3.2.4.

La operaci on NOT

La operaci on NOT es unaria (act ua sobre un solo operando, no como las operaciones binarias como AND ). El NOT de un bit es el valor opuesto del bit como se muestra en la Tabla 3.4. Sigue un c odigo de ejemplo:
1 2

mov not

ax, 0C123H ax

; ax = 3EDCH

Observe que NOT halla el complemento a 1. Diferente a las otras operaciones entre bits, la instrucci on NOT no cambian ninguno de los bits en el registro FLAGS.

54

CAP ITULO 3. OPERACIONES CON BITS X 0 1 NOT X 1 0

Cuadro 3.4: La operaci on NOT Prende el bit i Apaga el bit i OR the number with 2i (which is the binary number with just bit i on) AND el n umero binario con s olo el bit i apagado . Este operando es a menudo llamado m ascara XOR el n umero con 2i

Complementa el bit i

Cuadro 3.5: Usos de las operaciones booleanas

3.2.5.

La instrucci on TEST

La instrucci on TEST realiza una operaci on AND, pero no almacena el resultado. Solo ja las banderas del registro FLAGS dependiendo del resultado que fuera (muy parecido a lo que hace la instrucci on CMP con la resta que solo ja las banderas). Por ejemplo, si el resultado fuera cero, ZF se jar a.

3.2.6.

Usos de las operaciones con bits

Las operaciones con bits son muy u tiles para manipular bits individuales sin modicar los otros bits. La Tabla 3.5 muestra los 3 usos m as comunes de estas operaciones. Sigue un ejemplo de c omo implementar estas ideas.
1 2 3 4 5 6 7 8

mov or and xor or and xor xor

ax, ax, ax, ax, ax, ax, ax, ax,

0C123H 8 0FFDFH 8000H 0F00H 0FFF0H 0F00FH 0FFFFH

; ; ; ; ; ; ;

prende el bit 3, ax = C12BH apaga el bit 5, ax = C10BH invierte el bit 31, ax = 410BH prende el nibble, ax = 4F0BH apaga nibble, ax = 4F00H invierte nibbles, ax = BF0FH complemento a uno , ax = 40F0H

La operaci on AND se puede usar tambi en para hallar el residuo de una divisi on por una potencia de dos. Para encontrar el residuo de una divisi on i i por 2 , AND el n umero con una m ascara igual a 2 1. Esta m ascara contendr a unos desde el bit 0 hasta el bit i 1. Son solo estos bits los que contienen el residuo. El resultado de la AND conservar a estos bits y dejar a cero los otros. A continuaci on un fragmento de c odigo que encuentra el cociente y el residuo de la divisi on de 100 por 16.

3.3. EVITANDO SALTOS CONDICIONALES


1 2 3 4 5 6

55

mov mov count_loop: shl adc loop

bl, 0 ecx, 32 eax, 1 bl, 0 count_loop

; bl contendr a el n umero de bits prendidos ; ecx es el bucle contador ; se desplaza el bit en la bandera de carry ; a~ nade solo la bandera de carry a bl

Figura 3.3: Contando bits con ADC


1 2 3

mov mov and

eax, 100 ebx, 0000000FH ebx, eax

; 100 = 64H ; m acara = 16 - 1 = 15 or F ; ebx = residuo = 4

Usando el registro CL es posible modicar arbitrariamente bits. El siguiente es un ejemplo que ja (prende) un bit arbitrario en EAX. El n umero del bit a pender se almacena en BH.
1 2 3 4

mov mov shl or

cl, bh ebx, 1 ebx, cl eax, ebx

; first build the number to OR with ; se desplaza a la derecha cl veces ; prende el bit

Apagar un bit es solo un poco m as dif cil.


1 2 3 4 5

mov mov shl not and

cl, bh ebx, 1 ebx, cl ebx eax, ebx

; first build the number to AND with ; se desplaza a la derecha cl veces ; invierte los bits ; apaga el bit

El c odigo para complementar un bit arbitrario es dejado como ejercicio al lector. Es com un ver esta instrucci on en un programa 80X86. xor eax, eax ; eax = 0

A number XOR ed with itself always results in zero. This instruction is used because its machine code is smaller than the corresponding MOV instruction.

3.3.

Evitando saltos condicionales

Los procesadores modernos usan t ecnicas sosticadas para ejecutar el c odigo tan r apido como sea posible. Una t ecnica com un se conoce como

56

CAP ITULO 3. OPERACIONES CON BITS

ejecuci on especulativa . Esta t ecnica usa las capacidades de procesamiento paralelo de la CPU para ejecutar m ultiples instrucciones a la vez. Las instrucciones condicionales tienen un problema con esta idea. El procesador, en general, no sabe si se realiza o no la ramicaci on.Si se toma, se ejecutar a de instrucciones diferentes que si no se toma. El procesador trata de predecir si ocurrir a la ramicaci on. Si la predicci on fue err onea el procesador ha perdido su tiempo ejecutando un c odigo err oneo. Una manera de evitar este problema, es evitar usar ramicaciones condicionales cuando es posible. El c odigo de ejemplo en 3.1.5 muestra un programa muy simple donde uno podr a hacer esto. En el ejemplo anterior, los bits encendidos del registro EAX usa una ramicaci on para saltarse la instrucci on INC. La gura 3.3 muestra c omo se puede quitar la ramicaci on usando la instrucci on ADC para sumar el bit de carry directamente. Las instrucciones SETxx suministran una manera para suprimir ramicaciones en ciertos casos. Esta instrucci on ja el valor de un registro byte o un lugar de memoria a cero, basado en el estudio del registro FLAGS. Los caracteres luego de SET son los mismos caracteres usados en saltos condicionales. Si la condici on correspondiente de SETxx es verdadero, el resultado almacenado es uno, si es falso se almacena cero. Por ejemplo, setz al ; AL = 1 if Z flag is set, else 0

Usando estas instrucciones, uno puede desarrollar algunas t ecnicas ingeniosas que calculan valores sin ramicaciones. Por ejemplo, considere el problema de encontrar el mayor de dos valores. La aproximaci on 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. El programa de ejemplo de abajo muestra c omo se puede encontrar el mayor sin ninguna ramicaci on. For example, consider the problem of nding the maximum of two values. 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. The example program below shows how the maximum can be found without any branches. ; Archivo: max.asm %include "asm_io.inc" segment .data message1 db "Digite un n umero: ",0 message2 db "Digite otro n umero: ", 0 message3 db "El mayor n umero es: ", 0 segment .bss

1 2 3 4 5 6 7 8 9

3.3. EVITANDO SALTOS CONDICIONALES


10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47

57

input1

resd

; primer n umero ingresado

segment .text global _asm_main: enter 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

_asm_main 0,0 ; setup routine

eax, message1 print_string read_int [input1], eax eax, message2 print_string read_int ebx, eax, bl ebx ecx, ecx, ebx ebx, ecx, ebx [input1]

; imprime el primer mensaje ; ingresa el primer n umero

; imprime el segundo mensaje ; ingresa el segundo n umero (en ; ; ; ; ; ; ; ; ; ebx = 0 compara el segundo y el ebx = (input2 > input1) ebx = (input2 > input1) ecx = (input2 > input1) ecx = (input2 > input1) ebx = (input2 > input1) ebx = (input2 > input1) ecx = (input2 > input1) eax)

ebx eax [input1] ebx

primer n umero ? 1 : ? 0xFFFFFFFF : ? 0xFFFFFFFF : ? input2 : ? 0 : ? 0 : ? input2 :

0 0 0 0 0xFFFFFFFF input1 input1

eax, message3 print_string eax, ecx print_int print_nl

; imprime los resultado

eax, 0

; retorna a C

El truco es crear una m ascara que se pueda usar para seleccionar el valor mayor. La instrucci on SETG en la l nea 30 ja BL a 1. Si la segunda entrada es mayor o 0 en otro caso, esta no es la m ascara deseada. Para crear la

58

CAP ITULO 3. OPERACIONES CON BITS

m ascara que se r, la l nea requerida 31 usa la instrucci on NEG en el registro EBX. (Observe que se borr o EBX primero). Si EBX es 0 no hace nada; sin embargo si EBX es 1, el resultado es la representaci on en complemento a dos de -1 o 0xFFFFFFFF. Esta es la m ascara que se necesita. El resto del c odigo usa esta m ascara para seleccionar la entrada correcta como el mayor. Como truco alternativo usar la instrucci on DEC. En el c odigo de arriba, si NEG se reemplaza con un DEC, de nuevo el resultado ser a 0 o 0xFFFFFFFF. Sin embargo, los valores son invertidos que cuando se usa la instrucci on NEG.

3.4.
3.4.1.

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. La operaci on AND se representa con el operador 1 & . La operaci on OR se representa por el operador binario |. La operaci on XOR se representa con el operador binario ^ . Y la operaci on NOT se representa con el operador unario ~ . Los desplazamientos son realizados por C con los operadores binarios << y >> . El operador << realiza desplazamientos a la izquierda y el operador >> hace desplazamientos a la derecha. Estos operadores toman 2 operandos. El de la derecha es el valor a desplazar y el de la izquierda es el n umero de bits a desplazar. Si el valor a desplazar es un tipo sin signo, se realiza un desplazamiento l ogico. Si el valor es con signo (como int), entonces se usa un desplazamiento aritm etico. Sigue un ejemplo en C del uso de estos operadores: short int s ; short unsigned u; s = 1; u = 100; u = u | 0x0100; s = s & 0xFFF0; s = s u; u = u << 3; s = s >> 2; / se asume que short int es de 16 bits / / / / / / / / s = 0xFFFF (complemento a dos) / u = 0x0064 / u = 0x0164 / s = 0xFFF0 / s = 0xFE94 / u = 0x0B20 (desplazamiento olgico) / s = 0xFFA5 (desplazamiento earitmtico) /

3.4.2.

Usando las operaciones entre bits en C

Los operadores entre bits se usan en C para los mismos prop ositos que en lenguaje ensamblador. Ellos le permiten a uno manipular bits individuales
1

Este operador es diferente del operador binario && y del unario &!

3.4. MANIPULANDO BITS EN C Macro S IRUSR S IWUSR S IXUSR S IRGRP S IWGRP S IXGRP S IROTH S IWOTH S IXOTH Meaning el propietario puede leer el propietario puede escribir el propietario puede ejecutar el grupo de propietario puede leer el grupo del propietario puede escribir el grupo del propietario puede ejecutar los otros pueden leer los otros pueden escribir los otros pueden ejecutar

59

Cuadro 3.6: Macros POSIX para permisos de archivos y se pueden usar para multiplicaciones y divisiones r apidas. De hecho, un compilador de C inteligente usar a desplazamientos autom aticamente para multiplicaciones como X*=2, Muchos API2 (Como POSIX 3 y Win 32). tienen funciones que usan operandos que tienen datos codicados como bits. Por ejemplo, los sistemas POSIX mantienen los permisos de los archivos para 3 tipos diferentes de usuarios (un mejor nombre ser a propietario ), grupo y otros. A cada tipo de usuario se le puede conceder permisos para leer, escribir o ejecutar un archivo. Para cambiar los permisos de un archivo requiere que el programador de C manipule bits individuales. POSIX dene varios macros para ayudas (vea la tabla 3.6). La funci on chmod se puede usar para establecer los permisos de un archivo. Esta funci on toma dos par ametros, una cadena con el nombre del archivo sobre el que se va a actuar y un entero4 Con los bits apropiados para los permisos deseados. Por ejemplo, el c odigo de abajo ja los permisos para permitir que el propietario del archivo leerlo y escribirlo, a los usuarios en e, grupo leer en archivo y que los otros no tengan acceso. chmod(foo, S IRUSR | S IWUSR | S IRGRP ); La funci on POSIX stat se puede usar para encontrar los bits de permisos actuales de un archivo. Usada con la funci on chmod, es posible modicar algunos de los permisos sin cambiar los otros. Un ejemplo que quita el acceso a la escritura a los otros y a nade el acceso al propietario del archivo a la lectura. Los otros permisos no son alterados Los otros permisos no se alteran. struct stat le stats ; / estructura usada por stat () / stat (foo, & le stats ); / lee la o informacin del archivo .
Aplication Programming Interface Signica Portable Operatting System Interface for Computer Enviroments. Una norma desarrollada por el IEEE basado en UNIX. 4 Actualmente un par ametro de tipo mode t que es un typedef a un tipo integral.
3 2

60

CAP ITULO 3. OPERACIONES CON BITS

le stats .st mode holds permission bits / chmod(foo, ( le stats .st mode & S IWOTH) | S IRUSR);

3.5.

Representaciones Littel Endian y Big Endian

El cap tulo 1 introdujo el concepto de las representaciones big y little endian de datos multibyte. Sin embargo, el autor ha encontrad que este tema confunde a muchas personas. Esta secci on cubre el t opico con m as detalle. El lector recordar a que lo endian se reere al orden en que los bytes individuales se almacenan en memoria (no bits) de un elemento multibyte se almacena en memoria. Big endian es el m etodo m as directo. Almacena el byte m as signicativo primero, luego el siguiente byte en peso y as sucesivamente. En otras palabras los bits de m as peso se almacenan primero. Little endian almacena los bytes en el orden contrario (primero el menos signicativo). La familia de procesadores X86 usa la representaci on little endian. Como un ejemplo, considere la palabra doble 1234567816 . En la representaci on big endian, los bytes se almacenar an como 12 34 56 78. En la representaci on little endian los bytes se almacenar an como 78 56 34 12. El lector probablemente se preguntar a as mismo, por qu e cualquier dise nador sensato usa la representaci on little endian? Eran los ingenieros de Intel s adicos para inigir esta confusa representaci on a multitud de programadores? Parecer a que la CPU no tiene que hacer trabajo extra para almacenar los bytes hacia atr as en la memoria (e invertirlos cuando los lee de la memoria). La respuesta es que la CPU no hace ning un trabajo extra para leer y escribir en la memoria usando el formato little endian. O entiende que la CPU est a compuesta de muchos circuitos electr onicos que simplemente trabajan con bits. Los bits (y bytes) no est an en un orden en particular en la CPU. Considere el registro de 2 bytes AX. Se puede descomponer en registros de un byte (AH y AL). Hay circuitos en la CPU que mantienen los valores de AH y AL. Los circuitos no est an en un orden particular en la CPU. Esto signica que estos circuitos para AH no est an antes o despu es que los circuitos para AL. Quiere decir que, no es dif cil para la CPU hacer que almacene AH primero. El mismo argumento se aplica a los bits individuales dentro de un byte, no hay realmente ning un orden en los circuitos de la CPU (o la memoria). Sin embargo, ya que los bits individuales no se pueden direccionar directamente en la CPU o en la memoria, no hay manera de conocer que orden ellos parece que conservaran internamente en la CPU.

3.5. REPRESENTACIONES LITTEL ENDIAN Y BIG ENDIAN unsigned short word = 0x1234; / se asume sizeof( short) == 2 / unsigned char p = (unsigned char ) &word; if ( p[0] == 0x12 ) printf ( aMquina Big Endian\n); else printf ( aMquina Little Endian\n); Figura 3.4: C omo determinar lo Endianness

61

El c odigo en C en la gura 3.4 muestra c omo se puede determinar lo endian de una CPU. El apuntador p trata la variable word como dos elementos de un arreglo de caracteres. As , p [0] eval ua el primer byte de word en la memoria que depende de lo endian en la CPU. El c odigo en C en la Figura 3.4 muestra c omo se puede determinar lo endian de una CPU. El apuntador p trata la variable word como dos elementos de un arreglo de caracteres. As , p [0] eval ua el primer byte de word en la memoria que depende de lo endian en la CPU.

3.5.1.

Cuando tener cuidado con Little and Big Endian

Para la programaci on t pica, lo endian de la CPU no es importante. La mayor a de las veces esto es importante cuando se transere datos binarios entre sistemas de c omputo diferente. Esto es ocurre normalmente usando un medio de datos f sico (como un disco) o una red. Ya que el c odigo ASCII es de 1 byte la caracter stica endian no le es importante. Todos los encabezados internos de TCP/IP almacena enteros en big Endian (llamado orden de byte de la red ). Y las bibliotecas de suministran funciones de C para tratar la cuesti on endian de una manera port atil. Por ejemplo la funci on htonl () convierte una palabra doble ( o long int) del formato del host al de red. La funci on ntohl () hace la transformaci on inversa.5 Para un sistema big endian, las dos funciones s olo retornan su entrada sin cambio alguno. Esto le permite a uno escribir programas de res que compilar an y se ejecutar an correctamente en cualquier sistema sin importar lo endian. Para m as informaci on sobre lo endian y programaci on de redes vea el excelente libro de W. Richard Steven UNIX Network Programming. La Figura 3.5 muestra una funci on de C que invierte lo endian de una palabra doble. El 486 ha introducido una nueva instrucci on de m aquina
Ahora, invertir lo endian de un entero simplemente coloca al rev es los bytes; as convertir de big a little o de little a big es la misma operaci on. As , ambas funciones hacen la misma cosa.
5

Ahora con los conjuntos de caracteres multibyte como UNICODE , lo endian es importante a un para texto. UNICODE soporta ambos tipos de representaci on y tiene un mecanismo para especicar cu al se est a usando para representar los datos.

62

CAP ITULO 3. OPERACIONES CON BITS

unsigned invert endian ( unsigned x ) { unsigned invert ; const unsigned char xp = (const unsigned char ) &x; unsigned char ip = (unsigned char ) & invert; ip [0] ip [1] ip [2] ip [3] = = = = xp [3]; xp [2]; xp [1]; xp [0]; / invierte los bytes individuales /

return invert ; }

/ retorna los bytes invertidos /

Figura 3.5: Funci on invert endian llamada BSWAP que invierte los bytes de cualquier registro de 32 bits. Por ejemplo: bswap edx ; intercambia los bytes de edx

Esta instrucci on no se puede usar en los registros de 16 bits, sin embargo la instrucci on XCHG se puede usar para intercambiar los bytes en los registros de 16 bits que se pueden descomponer en registros de 8 bits. Por ejemplo: xchg ah,al ; intercambia los bytes de ax

3.6.

Contando bits

Primero se dio una t ecnica directa para contar el n umero de bits que est an encendidos en una palabra doble. Esta secci on mira otros m etodos menos directos de hacer esto, como un ejercicio de usar las operaciones de bits discutidas en este cap tulo.

3.6.1.

M etodo uno

El primer m etodo es muy simple, pero no obvio. La gura 3.6 muestra el c odigo. C omo trabaja este m etodo? En cada iteraci on el bucle, se apaga un bit del dato. Cuando todos los bits se han apagado (cuando el dato es cero), el bucle naliza. El n umero de iteraciones requerido para hacer el dato cero es igual al n umero de bits en el valor original del dato.

3.6. CONTANDO BITS int count bits ( unsigned int data ) { int cnt = 0; while ( data != 0 ) { data = data & (data 1); cnt++; } return cnt ; } Figura 3.6: Contando bits: m etodo uno

63

La l nea 6 es donde se apaga un bit del dato. C omo se hace esto? Considere la forma general de la representaci on en binario del dato y el 1 del extremo derecho de esta representaci on. Por denici on cada bit despu es de este 1 debe ser cero. Ahora, Cu al ser a la representaci on de data -1? Los bits a la izquierda del 1 del extremo derecho ser an los mismos que para data, pero en el punto del 1 del extremo derecho ellos ser an el complemento de los bits originales de data. Por ejemplo: data = xxxxx10000 data - 1 = xxxxx01111 donde X es igual para ambos n umeros. Cuando se hace data AND data -1, el resultado ser a cero el 1 del extremo derecho en data y deja todos los otros bits sin cambio.

3.6.2.

M etodo dos

Para contar bits se puede usar tambi en la b usqueda en una tabla de una palabra doble arbitraria. La aproximaci on directa ser a precalcular el n umero de bits para cada palabra doble y almacenar esto en un arreglo. Sin embargo, hay dos problemas relacionados con esta aproximaci on. Hay alrededor de 4 mil millones de palabras dobles! Esto signica que el arreglo ser a muy grande e iniciarlo consumir a mucho tiempo. (de hecho, a menos que uno vaya a utilizar realmente el arreglo de m as que 4 mil millones de veces, 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). Un m etodo m as real ser a precalcular la cantidad de bits para todas las posibilidades de un byte y almacenar esto en un arreglo. Entonces la palabra doble se puede dividir en 4 bytes. 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. La gura 3.7 muestra la implementaci on de esta

64

CAP ITULO 3. OPERACIONES CON BITS / lookup table /

static unsigned char byte bit count [256]; void initialize count bits () { int cnt , i , data;

for ( i = 0; i < 256; i ++ ) { cnt = 0; data = i ; while ( data != 0 ) { / emtodo uno / data = data & (data 1); cnt++; } byte bit count [ i ] = cnt; } } int count bits ( unsigned int data ) { const unsigned char byte = ( unsigned char ) & data; return byte bit count [ byte [0]] + byte bit count [ byte [1]] + byte bit count [ byte [2]] + byte bit count [ byte [3]]; } Figura 3.7: M etodo dos aproximaci on. La funci on initialize count bits debe ser llamada antes, del primer on inicia el arreglo global llamado a la funci on count bits . Esta funci byte bit count. La funci on count bits mira la variable data no como una palabra doble, sino como un arreglo de 4 bytes. El apuntador dword act ua como un apuntador a este arreglo de 4 bytes. As dword [0] es uno de los bytes en data ( el byte m as signicativo o el menos signicativo dependiendo si es little o big endian respectivamente). Claro est a uno podr a usar una instrucci on como: (data >> 24) & 0x000000FF Para encontrar el byte m as signicativo y hacer algo parecido con los otros bytes; sin embargo, estas construcciones ser an m as lentas que una referencia al arreglo. Un u ltimo punto, un bucle for se podr a usar f acilmente para calcular

3.6. CONTANDO BITS int count bits ( unsigned int x ) { static unsigned int mask[] = { 0x55555555, 0x33333333, 0x0F0F0F0F, 0x00FF00FF, 0x0000FFFF }; int i ; int shift ; / u nmero de posiciones a desplazarse a la derecha / for ( i =0, shift =1; i < 5; i ++, shift = 2 ) x = (x & mask[i ]) + ( ( x >> shift) & mask[i ] ); return x; } Figura 3.8: M etodo tres

65

la suma en las l neas 22 y 23. Pero el bucle for incluir a el trabajo extra de iniciar el ndice del bucle, comparar el ndice luego de cada iteraci on e incrementar el ndice. Calcular la suma como la suma expl cita de 4 valores ser a m as r apido. De hecho un compilador inteligente podr a convertir la versi on del bucle for a la suma expl cita. Este proceso de reducir o eliminar iteraciones de bucles es una t ecnica de optimizaci on conocida como loop unrolling.

3.6.3.

M etodo tres

Hay otro m etodo ingenioso de contar bits que est an en un dato. Este m etodo literalmente a nade los unos y ceros del dato unido. Esta suma debe ser igual al n umero de unos en el dato. Por ejemplo considere calcular los unos en un byte almacenado en una variable llamada data. El primer paso es hacer la siguiente operaci on: data = (data & 0x55) + ((data >> 1) & 0x55); Qu e hace esto? La constante hexadecimal 0X55 es 01010101 en binario. En el primer operando de la suma data es AND con el, los bits en las posiciones pares se sacan. El Segundo operando (data >> 1 & 0x55), primero mueve todos los bits de posiciones pares a impares y usa la misma m ascara para sacar estos mismos bits. Ahora, el primer operando contiene los bits pares y el segundo los bits impares de data. Cuando estos dos operandos se suman, se suman los bits pares e impares de data. Por ejemplo si data es 101100112 , entonces:

66

CAP ITULO 3. OPERACIONES CON BITS

00 01 00 01 01 01 00 01 01 10 00 10 La suma de la derecha muestra los bits sumados. Los bits del byte se dividen en 4 campos de 2 bits para mostrar que se realizan 4 sumas independientes. Ya que la mayor a de estas sumas pueden ser dos, no hay posibilidad de que la suma desborde este campo y da ne otro de los campos de la suma. Claro est a, el n umero total de bits no se ha calculado a un. Sin embargo la misma t ecnica que se us o arriba se puede usar para calcular el total en una serie de pasos similares. El siguiente paso podr a ser: or + data = (data & 0x33) + ((data >> 2) & 0x33); Continuando con el ejemplo de arriba (recuerde que data es ahora 011000102 ): data & 001100112 0010 0010 + (data >> 2) & 001100112 or + 0001 0000 0011 0010 Ahora hay 2 campos de 4 bits que se suman independientemente. El pr oximo paso es sumar estas dos sumas unidas para conformar el resultado nal: data = (data & 0x0F) + ((data >> 4) & 0x0F); 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. La Figura 3.8 muestra una implementaci on de este m etodo que cuenta los bits en una palabra doble. Usa un bucle for para calcular la suma. Podr a ser m as r apido deshacer el bucle; sin embargo, el bucle clarica c omo el m etodo generaliza a diferentes tama nos de datos.

data & 010101012 + (data >> 1) & 010101012

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). Las funciones y los procedimientos son ejemplos, en lenguajes de alto nivel de subprograma. El c odigo que llama en subprograma y el subprograma en s mismo deben estar de acuerdo en c omo se pasan los datos entre ellos. Estas reglas de c omo se pasar an el dato son llamadas convenciones de llamado. Una gran parte de este cap tulo tratar a de las convenciones de llamado est andares de C, que se pueden usar para interfasar subprogramas de ensamblador con programas de C. Estas (y otras convenciones) a menudo pasan direcciones de datos ( apuntadores) para permitirle al subprograma acceder a datos en la memoria.

4.1.

Direccionamiento indirecto

El direccionamiento indirecto le permite a los registros comportarse como variables apuntador. Para indicar que un registro se va a usar indirectamente como apuntador, se encierra entre par entesis cuadrados ([]) por ejemplo:
1 2 3

mov mov mov

ax, [Data] ebx, Data ax, [ebx]

; Direccionamiento directo de memoria de una palabra ; ebx = & Data ; ax = *ebx

Porque AX almacena una palabra, la l nea 3 lee una palabra comenzando en la direcci on almacenada en EBX. Si AX lee reemplazando con AX, se leer a un solo bate. Es importante notar que los registros no tienen ning un tipo como lo hacen las variables en C. Que EBX se asume que se nala est a totalmente determinada por qu e instrucciones se usan. Si EBX se utiliza incorrectamente; a menudo no habr a error en el ensamblador; sin embargo el programa no trabajar a correctamente. Esta es una de las muchas razones 67

68

CAP ITULO 4. SUBPROGRAMAS

por la cual el ensamblador es m as propenso a errores que los lenguajes de alto nivel. Todos los registros de 32 bits de prop osito general (EAX, EBX, ECX, EDX) y los registros de ndice (ESI y EDI) se pueden usar para el direccionamiento indirecto. En general los registros de 8 y 16 bits no.

4.2.

Sencillo subprograma de ejemplo

Un subprograma es una unidad independiente de c odigo que puede ser usada desde diferentes partes de un programa. En otras palabras, un subprograma es como una funci on en C. Se puede usar un salto para invocar el subprograma, pero el retorno representa un problema. Si el subprograma es usado en diferentes partes del programa debe retornar a la parte del c odigo desde la que se la invoc o. As , el salto de retorno desde el subprograma no puede ser a una etiqueta. El c odigo siguiente muestra c omo se puede realizar esto usando una forma indirecta de la instrucci on JMP. Esta forma de la instrucci on JMP usa el valor de un registro para determinar a d onde saltar (as , el registro se comporta como un apuntador a una funci on en C). Ac a est a el primer programa del cap tulo 1 reescrito para usarlo como subprograma.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21

sub1.asm ; file: sub1.asm ; Subprograma programa de ejemplo %include "asm_io.inc" segment prompt1 prompt2 outmsg1 outmsg2 outmsg3 .data db db db db db

"Ingrese un n umero: ", 0 "Ingrese otro n umero: ", 0 "Ud. ha ingresado ", 0 " y ", 0 ", la suma de ellos es ", 0

; no olvide el NULL

segment .bss input1 resd 1 input2 resd 1 segment .text global _asm_main: enter pusha

_asm_main 0,0 ; setup routine

4.2. SENCILLO SUBPROGRAMA DE EJEMPLO


22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63

69

mov call mov mov jmp ret1: mov call mov mov jmp mov add mov mov call mov call mov call mov call mov call mov call call

eax, prompt1 print_string ebx, input1 ecx, ret1 short get_int eax, prompt2 print_string ebx, input2 ecx, \$ + 7 short get_int eax, [input1] eax, [input2] ebx, eax eax, outmsg1 print_string eax, [input1] print_int eax, outmsg2 print_string eax, [input2] print_int eax, outmsg3 print_string eax, ebx print_int print_nl

; imprime el prompt

; almacena la direcci on de input1 en ebx ; almacena la direcci on de retorno en ecx ; lee un entero ; imprime el prompt

; ecx = esta direcci on + 7

; eax = palabra doble en input1 ; eax += palabra doble en input2 ; ebx = eax

; imprime el primer mensaje ; imprime input1 ; imprime el segundo mensaje ; imprime input2 ; imprime el tercer mensaje ; imprime sum (ebx) ; imprime nueva l nea

; ; ; ; ; ;

popa mov eax, 0 ; retorno a C leave ret subprogram get_int Parameters: ebx - address of dword to store integer into ecx - direcci on de la instrucci on a donde retornar Notes: value of eax is destroyed

70
64 65 66 67

CAP ITULO 4. SUBPROGRAMAS

get_int: call mov jmp

read_int [ebx], eax ecx

; almacena la entrada en memoria ; salta al llamador sub1.asm

El subprograma get int usa una convenci on de llamado simple basada en un registro. Ella espera que el registro EBX almacene la direcci on de la DWORD a almacenar el n umero de entrada y el registro ECX a almacenar el c odigo de la instrucci on a saltar. En las l neas 25 a 28, el operador $ se usa para calcular la direcci on de retorno. El operador $ retorna la direcci on actual para la l nea en que aparece. La expresi on $ + 7 calcula la direcci on de la instrucci on 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 dena 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 umero a a nadirle a $ podr a no ser 7! Afortunadamente hay una manera mucho m as 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 est a organizada de esta manera. La instrucci on PUSH a nade datos a la pila y la instrucci on POP quita datos. El dato extra do siempre es el u ltimo dato insertado (esta es la raz on por la cual es llamado FIFO). El registro de segmento SS especica el segmento de datos que contiene la pila. (Normalmente este es el mismo segmento de datos). El registro ESP contiene la direcci on del dato que ser a quitado de la pila. Los datos s olo se pueden a nadir en unidades de palabras dobles. Esto es, que no se puede insertar un solo byte en la pila. La instrucci on PUSH inserta una palabra doble1 en la pila rest andole 4 a ESP y entonces almacena la palabra doble en [ESP]. La instrucci on POP lee la palabra doble almacenada en [ESP] y luego a nade 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 push push

dword 1 dword 2 dword 3

; 1 almacendao en 0FFCh, ESP = 0FFCh ; 2 almacenado en 0FF8h, ESP = 0FF8h ; 3 almacenado en 0FF4h, ESP = 0FF4h

1 Actualmente tambi en se pueden empujar palabras, pero en el modo protegido de 32 bits es mejor trabajar s olo con palabras dobles en la pila.

4.4. LAS INSTRUCCIONES CALL Y RET


4 5 6

71

pop pop pop

eax ebx ecx

; EAX = 3, ESP = 0FF8h ; EBX = 2, ESP = 0FFCh ; ECX = 1, ESP = 1000h

La pila se puede usar como un almac en de datos temporal muy conveniente. Tambi en se usa para el llamado a subprogramas, pasando par ametros y variables locales. El 80x86 tambi en suministra la instrucci on PSHA que empuja el valor de los registros: EAX, EBX, ECX, EDX, ESI, EOI y EBP (no en este orden). La instrucci on POPA se puede usar para devolver todos estos registros a su valor anterior.

4.4.

Las instrucciones CALL y RET

El 80X86 suministra dos instrucciones que usa la pila para hacer llamados a subprogramas r apido y f acil. La instrucci on CALL hace un salto incondicional a un subprograma y empuja en la pila la direcci on de la pr oxima instrucci on. La instrucci on RET saca una direcci on de la pila y salta a esta direcci on. Cuando se usa esta instrucci on, es muy importante que uno administre la pila correctamente ya que la instrucci on RET debe extraer de la pila el n umero correcto. El programa anterior se puede reescribir usando estas nuevas instrucciones cambiando las l neas 25 a 34 por: mov call mov call ebx, input1 get_int ebx, input2 get_int

y cambiando el subprograma get int a: get_int: call mov ret

read_int [ebx], eax

Hay varias ventajas de CALL y RET Es simple Permite a los subprogramas hacer llamados anidados f acilmente. Observe que get int llama read int. Esta llamada empuja otra direcci on

72

CAP ITULO 4. SUBPROGRAMAS en la pila. Al nal del c odigo de red int hay un RET que saca la direcci on de retorno y que salta de nuevo al c odigo de get int. Cuando on de retorno la instrucci on RET de get int se ejecuta, saca la direcci que salta de nuevo a asm main. Esto trabaja correctamente por la propiedad LIFO de la pila.

Recuerde es muy importante sacar todos los datos que se han empujado en la pila. Por ejemplo considere lo siguiente:
1 2 3 4 5

get_int: call mov push ret

read_int [ebx], eax eax ; <<saca el valor de EAX, no la direcci on de retorno!!

Este c odigo no retornar a correctamente.

4.5.

Convenciones de llamado

Cuando un subprograma se invoca, el c odigo llamado y el subprograma (el llamador ) deben estar de acuerdo en c omo se pasan datos entre ellos. Los lenguajes de alto nivel tienen maneras est andares de pasarse datos, conocidas como convenciones de llamado. Para interfasar c odigo de alto nivel con lenguaje ensamblador, este debe usar las mismas convenciones que el lenguaje de alto nivel. Las convenciones de llamado pueden diferir de compilador a compilador o puede variar de c omo se compila el c odigo (si se ha optimizado o no). Una convenci on universal es que el c odigo ser a invocado con la instrucci on CALL y retornar a con RET. Todos los compiladores de C para PC soportan una convenci on de llamado que ser a descrito en el resto del cap tulo por etapas. Estas convenciones le permiten a uno crear subprogramas que sean reentrantes. Un subprograma reentrante puede ser llamado en cualquier punto del programa con seguridad (a un dentro del subprograma mismo).

4.5.1.

Pasando par ametros en la pila

Los par ametros a un subprograma se pueden pasar en la pila. Ellos se empujan en la pila antes de la instrucci on CALL. Tal como en C, si el par ametro es cambiado por el subprograma se debe pasar la direcci on del dato no su valor. Si el tama no del par ametro es menor que una palabra doble, se debe convertir a palabra doble antes de ser empujada en la pila. Los par ametros no son sacados de la pila por el subprograma, en lugar de ello son accedidos desde la pila misma.Por qu e?

4.5. CONVENCIONES DE LLAMADO

73

ESP + 4 ESP

Par ametro Direcci on de retorno Figura 4.1:

ESP + 8 ESP + 4 ESP

Par ametro Direcci on de retorno datos del subprograma Figura 4.2:

Ya que ellos se han empujado a la pila antes de la instrucci on CALL, la direcci on de retorno tendr a que haberse sacado primero (y luego metido otra vez). A menudo los par ametros tendr an que usarse en varios lugares en el subprograma. Normalmente, ellos no se pueden dejar en un registro durante todo el subprograma y tendr a que almacenarse en memoria. Dej andolos en la pila tenemos una copia del dato en memoria que se puede acceder en cualquier parte del subprograma. Considere un subprograma al que se le pasa un solo par ametro en la pila. Cuando el subprograma se invoca, la pila se ve como en la Figura 4.1. Se puede acceder al par ametro usando direccionamiento indirecto ([ESP+4])2 . Si la pila se usa dentro del subprograma para almacenar datos, el n umero necesario a ser agregado a ESP cambiar a. Por ejemplo, la Figura 4.2 muestra c omo se ve la pila si una palabra doble se empuja en ella. Ahora el par ametro es ESP + 8 y no ESP + 4. As , esto puede ser muy propenso a errores usar ESP cuando uno se reere a par ametros. Para resolver este problema, el 80386 suministra otro registro para usar: EBP. El u nico prop osito de este registro es referenciar datos en la pila. La convenci on 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 le permite a ESP cambiar cuando los datos se empujen o se saquen de la pila sin modicar EBP. Al nal del subprograma, se debe restaurar el valor de EBP (esta es la raz on por la cual se guarda el valor al principio del subprograma). La Figura 4.3 muestra la forma general de un subprograma que sigue estas convenciones.
Es v alido a nadir una constante a un registro cuando se usa direccionamiento indirecto. Se pueden construir expresiones m as complicadas tambi en. Este t opico se ver a en el cap tulo siguiente.
2

Cuando se usa direccionamiento indirecto, el procesador 80X86 accede a segmentos diferentes dependiendo de qu e registros se usan en la expresi on de direccionamiento indirecto. ESP (y EBP usan el segmento de la pila) mientras que EAX, EBX, ECX y EDX usan el segmento de datos. Sin embargo, esto normalmente no tiene importancia para la mayor a de los programas en modo protegido, porque para ellos los segmentos de datos y de pilas son los mismos.

74
1 2 3 4 5 6

CAP ITULO 4. SUBPROGRAMAS

subprogram_label: push ebp mov ebp, esp ; subprogram code pop ebp ret

; guarda el valor original de EBP en la pila ; nuevo EBP = ESP ; restaura el valor original de EBP

Figura 4.3: Forma general de un subprograma

ESP + 8 ESP + 4 ESP

EBP + 8 EBP + 4 EBP

Par ametro Direcci on de retorno EBP guardado

Figura 4.4: Las l neas 2 y 3 de la Figura 4.3 componen el pr ologo general de un subprograma. Las l neas 5 y 6 conforman el ep logo. La gura 4.4 muestra c omo se ve la pila inmediatamente despu es del pr ologo. Ahora los par ametros se pueden acceder con [EBP + 8] en cualquier lugar del subprograma sin importar qu e haya empujado en la pila el subprograma. Luego que el subprograma culmina, los par ametros que se empujan en la pila se deben quitar. La convenci on de llamado de C especica que el c odigo llamador debe hacer esto. Otras convenciones son diferentes. Por ejemplo la convenci on de llamado de Pascal especica que el subprograma debe quitar los par ametros de la pila (hay otra forma de la instrucci on RET que hace esto f acil). Algunos compiladores de C soportan esta convenci on tambi en. El identicador pascal se usa en la denici on del prototipo de la funci on para decirle al compilador que use esta convenci on. De hecho, la convenci on stdcall que usan las funciones de C del API de MS Windows trabajan de esta forma. Cu al es la ventaja de este modo? Es un poco m as eciente que la convenci on de llamado de C. Por qu e todas las funciones no usan esta convenci on entonces? En general C le permite a una funci on tener un n umero variable de argumentos(printf y scanf son ejemplos). Para este tipo de funciones, la operaci on de quitar los par ametros de la pila variar a de un llamado de la funci on a otra. La convenci on de C permite las instrucciones para realizar esta operaci on f acilmente de un llamado a otro. Las convenciones de Pascal y stdcall hacen esta operaci on muy dif cil. As , la convenci on de Pascal (como el lenguaje Pascal) no permite este tipo de funciones. MS Windows puede usar esta convenci on ya que ninguna de las funciones del API toma un n umero variable de argumentos.

4.5. CONVENCIONES DE LLAMADO


1 2 3

75

push call add

dword 1 fun esp, 4

; pasa 1 como par ametro ; quita el par ametro de la pila

Figura 4.5: Muestra del llamado a un subprograma La Figura 4.5 muestra como ser a invocado un subprograma usando la convenci on de llamado de C. La l nea 3 quita los par ametros de la pila manipulando directamente el apuntador de la pila. Una instrucci on POP se podr a usar para hacer esto pero requerir a que el resultado in util se almacene en un registro. Actualmente, para este caso en particular muchos compiladores podr an usar una instrucci on POP ECX para quitar el par ametro. El compilador usar a POP en lugar de ADD porque ADD requiere m as bytes para la instrucci on. Sin embargo, POP tambi en altera el valor de ECX. Luego est a otro programa de ejemplo con dos subprogramas que usan la convenci on de llamado de C discutida arriba. La l nea 54 (y otras l neas) muestran que se pueden declarar varios segmentos de datos y texto en un solo archivo fuente. Ellos ser an combinados en un solo segmento de texto y datos en el proceso de encadenamiento. Dividir el c odigo y los datos en segmentos separados permite que los datos de un subprograma se denan cerca del c odigo del subprograma. sub3.asm

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18

%include "asm_io.inc" segment .data sum dd 0 segment .bss input resd 1

; ; pseudo-c odigo ; i = 1; ; sum = 0; ; while( get_int(i, &input), input != 0 ) { ; sum += input; ; i++; ; } ; print_sum(num); segment .text

76
19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60

CAP ITULO 4. SUBPROGRAMAS _asm_main 0,0 ; setup routine

global _asm_main: enter pusha mov while_loop: push push call add mov cmp je add inc jmp end_while: push call pop popa leave ret

edx, 1 edx dword input get_int esp, 8 eax, [input] eax, 0 end_while [sum], eax edx short while_loop

; edx es i en el pseudoc odigo ; guarda i en la pila ; empuja la direcci on de input en la pila ; quita i e &input de la pila

; sum += input

dword [sum] print_sum ecx

; empuja el valor de sum ; quita [sum] de la pila

en la pila

; subprograma get_int ; Param etros (en el orden que es empujan en la pila) ; n umero de input (en [ebp + 12]) ; address of word to store input into (at [ebp + 8]) ; Notas: ; Los valores de eax y ebx se destruyen segment .data prompt db ") Ingrese un entero (0 para salir): ", 0 segment .text get_int: push

ebp

4.5. CONVENCIONES DE LLAMADO


61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98

77

mov mov call mov call call mov mov pop ret

ebp, esp eax, [ebp + 12] print_int eax, prompt print_string read_int ebx, [ebp + 8] [ebx], eax ebp ; retorna al llamador

; almacena input en memoria

; subprograma print_sum ; imprime la suma ; Parameter: ; suma a imprimir (en [ebp+8]) ; Nota: destrueye el valor de eax ; segment .data result db "La suma es ", 0 segment .text print_sum: push mov mov call mov call call pop ret

ebp ebp, esp eax, result print_string eax, [ebp+8] print_int print_nl ebp sub3.asm

78
1 2 3 4 5 6 7 8

CAP ITULO 4. SUBPROGRAMAS

subprogram_label: push ebp mov ebp, esp sub esp, LOCAL_BYTES ; subprogram code mov esp, ebp pop ebp ret

; guarda el valor original de EBP en la pila ; nuevo EBP = ESP ; = # de bytes necesitados por las variables locales ; libera las variables locales ; restaura el valor original de EBP

Figura 4.6: Forma general de un subprograma con variables locales void calc sum( int n , int sump ) { int i , sum = 0; for ( i =1; i <= n; i++ ) sum += i; sump = sum; } Figura 4.7: versi on de C de sum

4.5.2.

Variables locales en la pila

La pila se puede usar como un lugar adecuado para las variables locales. Ah es exactamente donde C almacena las variables normales (o automatic como se dice en C). Usar la pila para las variables es importante si uno desea que el programa sea reentrante. Un programa reentrante trabajar a si es invocado en cualquier lugar, incluido en subprograma en s mismo. En otras palabras, los programas reentrantes pueden ser invocados recursivamente. Usar la pila para las variables tambi en ahorra memoria. Los datos no almacenados en la pila est an usando la memoria desde el comienzo hasta el nal del programa (C llama este tipo de variable globalo static ). Los datos almacenados en la pila solo usan la memoria cuando el subprograma que los dene est a activo. Las variables locales son almacenadas justo despu es que se guard o el valor EBP en la pila. Ellas son colocadas restando el n umero de bytes requeridos de ESP en el pr ologo del subprograma. La Figura 4.6 muestra el nuevo esqueleto del subprograma. El registro EBP se usa para acceder a las variables locales. Considere la funci on de C en la Figura 4.7. La Figura 4.8 muestra c omo se podr a escribir un programa equivalente en ensamblador.

4.5. CONVENCIONES DE LLAMADO


1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23

79

cal_sum: push mov sub mov mov for_loop: cmp jnle add inc jmp end_for: mov mov mov mov pop ret

ebp ebp, esp esp, 4 dword [ebp - 4], 0 ebx, 1 ebx, [ebp+8] end_for [ebp-4], ebx ebx short for_loop

; hace espacio para la sum local ; sum = 0 ; ebx (i) = 1 ; es i <= n?

; sum += i

ebx, [ebp+12] eax, [ebp-4] [ebx], eax esp, ebp ebp

; ebx = sump ; eax = sum ; *sump = sum;

Figura 4.8: Versi on en ensamblador de sum

La Figura 4.9 muestra como se ve la pila luego del pr ologo del programa en la Figura 4.8. Esta parte de la pila que contiene la informaci on de retorno, los par ametros y las variables locales es llamado marco de pila (stack frame). Cada invocaci on a una funci on de C crea un nuevo marco de la pila en la pila. A pesar del hecho que El pr ologo y el ep logo de un subprograma se pueden simplicar usando dos instrucciones especiales que est an dise nadas espec camente para este prop osito. La instrucci on ENTER ejecuta el c odigo del pr ologo y LEAVE ejecuta el ep logo. La instrucci on ENTER toma dos operandos inmediatos. Para la convenci on de llamado de C, el segundo operando es siempre 0. El primer operando es el n umero de bytes necesarios para las variables locales. La instrucci on LEAVE no tiene operandos. La Figura 4.10 muestra c omo se usan estas instrucciones. Observe que el programa esqueleto (Figura 1.7) tambi en usa ENTER y LEAVE.
ENTER y LEAVE simplican el pr ologo y el ep logo ellos no se usan muy a menudo. Por qu e? Porque ellas son m as lentas que instrucciones equivalentes m as simples. Este es un ejemplo de cuando uno no puede asumir que una sola instrucci on es m as r apida que una secuencia de instrucciones.

80 ESP ESP ESP ESP ESP + + + + 16 12 8 4 EBP EBP EBP EBP EBP + 12 +8 +4 -4

CAP ITULO 4. SUBPROGRAMAS sump n Direcci on de retorno EBP guardado sum

Figura 4.9:
1 2 3 4 5 6

subprogram_label: enter LOCAL_BYTES, 0 ; subprogram code leave ret

; = n umero de bytes necesitados por las ; variables locales

Figura 4.10: Forma general de un subprograma con variables locales usando ENTER and LEAVE The prologue and epilogue of a subprogram can be simplied by using two special instructions that are designed specically for this purpose. The ENTER instruction performs the prologue code and the LEAVE performs the epilogue. The ENTER instruction takes two immediate operands. For the C calling convention, the second operand is always 0. The rst operand is the number bytes needed by local variables. The LEAVE instruction has no operands. Figure 4.10 shows how these instructions are used. Note that the program skeleton (Figure 1.7) also uses ENTER and LEAVE.

4.6.

Programas Multinm odulo

Un programa multim odulo es uno que est a compuesto de m as de un archivo objeto. Todos los programas presentados ac a han sido multim odulo. Ellos est an compuestos del archivo objeto driver y el archivo objeto de ensamblador (m as los archivos objeto de las bibliotecas de C). Recuerde que el encadenador combina los archivos objeto en un solo programa ejecutable. El encadenador debe emparejar las referencias hechas para cada etiqueta en un m odulo (archivo objeto) con su denici on en otro m odulo. Para que el m odulo A use la etiqueta denida en el m odulo B, se debe usar la directiva extern. Luego de la directiva extern viene una lista de etiquetas separadas por comas. La directiva le dice al ensamblador que trate esas etiquetas como externas al m odulo. O sea, esas son etiquetas que se pueden usar en este m odulo pero est an denidas en otro. El archivo asm io.inc dene las

4.6. PROGRAMAS MULTINMODULO

81

rutinas read int, etc. como externas. En ensamblador, no se puede acceder externamente a las etiquetas por defecto. Si una etiqueta puede ser accedida desde otros m odulos diferentes al cual se deni o, debe declararse global en su m odulo. La directiva global hace esto, la l nea 13 del programa esqueleto listado en la Figura 1.7 muestra que la etiqueta asm main est a denida como global. Sin esta declaraci on, deber a haber error del encadenador. Por qu e? Porque el c odigo de C no podr a hacer referencia a la etiqueta interna asm main. Luego est a el c odigo para el ejemplo anterior, reescrito para usar dos m odulos. Los dos subprogramas (get int y print sum) est an en archivos fuentes diferentes que la rutina asm main. %include "asm_io.inc" segment .data sum dd 0 segment .bss input resd 1 segment .text global extern _asm_main: enter pusha mov while_loop: push push call add mov cmp je add inc jmp main4.asm

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30

_asm_main get_int, print_sum 0,0 ; setup routine

edx, 1 edx dword input get_int esp, 8 eax, [input] eax, 0 end_while [sum], eax edx short while_loop

; edx es i en el pseudoc odigo ; guarda i en la pila ; empuja la direcci on input en la pila ; quita i e &input de la pila

; sum += input

82
31 32 33 34 35 36 37 38 39

CAP ITULO 4. SUBPROGRAMAS

end_while: push call pop popa leave ret

dword [sum] print_sum ecx

; empuja el valor de sum de la pila ; quita sum de la pila

main4.asm sub4.asm

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32

%include "asm_io.inc" segment .data prompt db segment .text global get_int: enter mov call mov call call mov mov leave ret segment .data result db segment .text print_sum: enter mov call

") Ingrese un n umero entero (0 para salir): ", 0

get_int, print_sum 0,0 eax, [ebp + 12] print_int eax, prompt print_string read_int ebx, [ebp + 8] [ebx], eax

; almacena la entrada en memoria

; retorna

"La suma es ", 0

0,0 eax, result print_string

4.7. INTERFAZANDO ENSAMBLADOR CON C


33 34 35 36 37 38 39

83

mov call call leave ret

eax, [ebp+8] print_int print_nl

sub4.asm

El ejemplo anterior solo tiene etiquetas de c odigo global sin embargo las etiquetas de datos trabajan exactamente de la misma manera.

4.7.

Interfazando ensamblador con C

Hoy d a, pocos programas est an escritos completamente en ensamblador. Los compiladores son muy buenos en convertir c odigo de alto nivel en un c odigo de m aquina eciente. Ya que es mucho m as f acil escribir c odigo en un lenguaje de alto nivel, es m as popular. Adem as, el c odigo de alto nivel es mucho m as port atil que el ensamblador. Cuando se usa ensamblador, se usa a menudo para peque nas partes de c odigo. Esto se puede hacer de dos maneras: llamando rutinas de ensamblador desde C o ensamblado en l nea. El ensamblado en l nea le permite al programador colocar instrucciones de ensamblador directamente en el c odigo de C. Esto puede ser muy conveniente; sin embargo hay desventajas del ensamblado en l nea. El c odigo en ensamblador se debe escribir en el formato que usa el compilador. No hay compilador que en el momento soporte el formato NASM. Los diferentes compiladores requieren diferentes formatos. Borland y Microsoft requieren el formato NASM . DJGPP y el gcc de Linux requieren el formato GAS3 . La t ecnica de llamar una rutina en ensamblador est a mucho m as generalizada en el PC. Las rutinas de ensamblador com unmente 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. La rutina debe ser lo m as r apida posible y el programador puede optimizar a mano el c odigo mejor que el compilador La u ltima raz on no es tan v alida como una vez lo fue. La tecnolog a de los compiladores se ha mejorado con los a nos y a menudo generan un c odigo muy eciente. (Especialmente si se activan las optimizaciones del compilador).
3 GAS es el ensamblador que usan todos los compiladores GNV. Usa la sintaxis AT&T que es muy diferente de la sintaxis relativamente similares de MASM, TASM y NASM.

84
1 2 3 4 5 6 7 8 9 10

CAP ITULO 4. SUBPROGRAMAS

segment .data x dd format db

0 "x = %d\n", 0

segment .text ... push dword [x] push dword format call _printf add esp, 8

; ; ; ;

empuja el valor de x empuja la direcci on de la cadena con formato observe el gui on bajo quita los par ametros de la pila

Figura 4.11: Llamado a printf EBP + 12 EBP + 8 EBP + 4 EBP valor de x direcci on de la cadena con formato Direcci on de retorno EBP guardado

Figura 4.12: Pila dentro de printf Las desventajas de las rutinas en ensamblador son: portabilidad reducida y lo poco legible. La mayor a de las convenciones de llamado ya se han especicado. Sin embargo hay algunas caracter sticas adicionales que necesitan ser descritas.

4.7.1.
La palabra reservada register se puede usar en una declaraci on de una variable de C para sugerirle al compilador que use un registro para esta variable en vez de un lugar en memoria. Ellas se conocen como variables register. Los compiladores modernos hacen esto autom aticamente sin requerir ninguna sugerencia.

Ahorrando registros

Primero, C asume que una subrutina conserva los valores de los siguientes registros: EBX, ESI, EDI, EBP, CS, DS, SS, ES. Esto no signica que la subrutina no puede cambiarlos internamente. En vez de ello, signica que si se hace un cambio en sus valores, deben restablecer los valores originales antes que la subrutina retorne. Los valores en EBX, ESI y EDI no se deben modicar porque C usa esos registros para variables register. Normalmente la pila se usa para guardar los valores originales de estos registros.

4.7.2.

Etiquetas de funciones

La mayor a de compiladores anteponen un gui on bajo ( ) al inicio de los nombres de funciones y variables globales o static. Por ejemplo una funci on llamada f se le asignar a la etiqueta f. As , si esta es una rutina en ensamblador se debe llamar f no f. El compilador gcc de Linux no antepone ning un car acter. Bajo los ejecutables ELF de Linux, uno simplemente usar a

4.7. INTERFAZANDO ENSAMBLADOR CON C

85

la etiqueta f para la funci on de C f. Sin embargo el gcc de DJGPP antepone un gui on bajo. Observe que en el programa esqueleto de ensamblador (Figura 1.7) la etiqueta de la rutina principal es asm main.

4.7.3.

Pasando par ametros

Bajo la convenci on de llamado de C, los argumentos de una funci on se empujan en la pila en el orden inverso que aparecen en el llamado a la funci on. Considere la siguiente instrucci on en C: printf ("x=%d/n",x); la Figura 4.11 muestra como se compilar a esto (mostrado en el formato equivalente de NASM). La Figura 4.12 muestra c omo se ve la pila luego del pr ologo dentro de la funci on printf. La funci on printf es una de las funciones de la biblioteca de C que puede tomar cualquier n umero de argumentos. Las reglas de la convenci on de llamado de C fueron escritas espec camente para permitir este tipo de funciones. Ya que la direcci on de la cadena con formato se empuja de u ltimo, este lugar en la pila ser a siempre EBP + 8 no importa cuantos par ametros se le pasan a la funci on. El c odigo printf puede ver en la cadena con formato cu antos par ametros se le debieron haber pasado y verlos en la en la pila. Claro est a, si se comete un error, printf ("x=%d/n"); el c odigo de printf esperar a imprimir una palabra doble en [EBP+12]. Sin embargo este no ser a el valor de x.

No es necesario usar ensamblador para procesar un n umero arbitrario de par ametros en C. El archivo de cabecera stdarg.h dene marcos que se pueden usar para procesarlos con portabilidad. Vea cualquier buen libro de C para los detalles

4.7.4.

Calculando las direcciones de las variables locales

Hallar la direcci on de una variable local denida en el segmento data o bss es sencillo. Sin embargo calcular la direcci on de una variable local (o par ametro) en la pila no es directo. Sin embargo, es una necesidad muy com un cuando se llaman subrutinas. Considere el caso de pasar la direcci on de una variable (la llamaremos x) a una funci on (que llamaremos foo). Si x est a en EBP 8 en la pila, uno no puede usar: mov eax, ebp - 8

Por qu e? El valor que MOV almacena en EAX debe ser calculado por el ensamblador (esto es, debe ser una constante). Sin embargo, hay una instrucci on que hace el c alculo deseado. Es llamado LEA (Load Efective Adress ). Lo siguiente calcular a la direcci on de x y la almacena en EAX: lea eax, [ebp - 8]

Ahora EAX almacena la direcci on de x y podr a ser empujada en la pila cuando se llame la funci on foo. No se confunda, parece como si esta instrucci on est a leyendo el dato en [EBP8]; sin embargo esto no es verdad. La

86

CAP ITULO 4. SUBPROGRAMAS

instrucci on LEA nunca lee la memoria! Solo calcula la direcci on que ser a le da por otra instrucci on y almacena esta direcci on en el primer operando de registro. Ya que no se hace ninguna lectura a memoria, no hay necesidad, ni est a permitido denir el tama no de la memoria (dword).

4.7.5.

Retornando valores

Las funciones diferentes a void retornan un valor. La convenci on de llamado de C especica c omo se hace esto. Los valores de retorno se pasan a trav es de registros. Todos los tipos enteros (char, int, enum, etc.) se retornan en el registro EAX. Si son m as peque nos que 32 bits, ellos son extendidos a 32 bits cuando se almacenan en EAX. (el c omo se extienden depende de si ellos tipos son con o sin signo.) Los valores de 64 bits se retornan en el par de registros EDX:EAX. Los valores tipo apuntador tambi en se almacenan en EAX. Los valores de punto otante son almacenados en el registro ST0 del coprocesador matem atico. (Este registro se discute en el cap tulo de punto otante).

4.7.6.

Otras convenciones de llamado

Las reglas anteriores describen la convenci on de llamado est andar de C que es soportada por todos los compiladores de C para 80x86. A menudo los compiladores soportan otras convenciones de llamado tambi en. Cuando se interfaza con lenguaje ensamblador es muy importante conocer que convenci on de llamado est a usando el compilador cuando llama su funci on. Normalmente, el defecto es usar la convenci on de llamado est andar; sin embargo no siempre es este el caso4 . Los compiladores que usan varias convenciones a menudo tienen opciones de la l nea de ordenes que se pueden usar para cambiar la convenci on por defecto. Ellos tambi en suministran extensiones a la sintaxis de C para asignar expl citamente convenciones de llamado a funciones individuales. Sin embargo, estas extensiones no est an normalizadas y pueden variar de un compilador a otro. El compilador GCC permite diferentes convenciones de llamado. La convenci on de una funci on se puede declarar expl citamente usando la extensi on attribute . Por ejemplo para declarar una funci on void que usa la convenci on de llamado est andar llamada f que toma un par ametro int, use la siguiente sintaxis para su prototipo: void f ( int )
4

attribute (( cdecl ));

El compilador de C de Watcom es un ejemplo de uno que no usa la convenci on de llamado est andar por defecto. Vea el c odigo fuente de ejemplo para Watcom para los detalles

4.7. INTERFAZANDO ENSAMBLADOR CON C

87

GCC tambi en soporta la convenci on de llamado est andar . La funci on de arriba se podr a declarar para usar esta convenci on reemplazando cdecl con stdcall. La diferencia entre stdcall y cdecl es que stdcall requiere que la subrutina quite los par ametros de la pila (como lo hace la convenci on de llamado de Pascal). As , la convenci on stdcall s olo se puede usar con funciones que tomen un n umero jo de par ametros (ej unas diferentes a printf y scanf). GCC tambi en soporta un atributo adicional regparam que le dice al compilador que use los registros para pasar hasta 3 argumentos enteros a su funci on en lugar de usar la pila. Este es un tipo com un de optimizaci on que soportan muchos compiladores. Borland y Microsoft usan una sintaxis com un para declarar convenciones de llamado. Ellas a naden a las palabras reservadas cdecl stdcall a C. Estas palabras reservadas act uan como modicadoras de funciones y aparecen inmediatamente antes nombre de la funci on en un prototipo. Por ejemplo, la funci on f de arriba se podr a denir para Borland y Microsoft as : void cdecl f ( int );

Hay ventajas y desventajas para cada convenci on de llamado. La principal ventaja de la convenci on cdecl es que es simple y muy exible. Se puede usar para cualquier tipo de funci on de C y cualquier compilador de C. Usar otras convenciones puede limitar la portabilidad de la subrutina. Su principal desventaja es que puede ser m as lenta que alguna de las otras y usa m as memoria (ya que cada invocaci on de la funci on requiere c odigo para quitar los par ametros de la pila). Las ventajas de la convenci on stdcall es que usa menos memoria que cdecl. No se requiere limpiar la pila despu es de la instrucci on CALL. La principal desventaja es que no se puede usar con funciones que tengan un n umero variable de argumentos. La ventaja de usar una convenci on que use registros para pasar enteros es la velocidad. La principal desventaja es que la convenci on es m as compleja. Algunos par ametros pueden estar en los registros y otros en la pila.

4.7.7.

Ejemplos

El siguiente es un ejemplo c omo 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.7) o el m odulo driver.c) main5.c #include <stdio.h> / prototipo para la rutina en ensamblador /

88 void calc sum( int , int ) int main( void ) { int n , sum; printf (Sumar enteros hasta : ); scanf( %d, &n); calc sum(n, &sum); printf (Sum is %d\n, sum); return 0; }

CAP ITULO 4. SUBPROGRAMAS attribute (( cdecl ));

main5.c ; ; ; ; ; ; ; ; ; ; ; ; ; sub5.asm subroutinea _calc_sum halla la suma de los enteros de 1 hasta n Parametros: n - hasta d onde sumar (en [ebp + 8]) sump - apuntador a un int para almacenar sum (en [ebp + 12]) pseudoc odigo en C: void calc_sum( int n, int * sump ) { int i, sum = 0; for( i=1; i <= n; i++ ) sum += i; *sump = sum; }

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27

segment .text global _calc_sum ; ; local variable: ; sum at [ebp-4] _calc_sum: enter 4,0 push ebx mov dword [ebp-4],0 dump_stack 1, 2, 4 mov ecx, 1 for_loop:

; hace espacio para sum en la pila ; IMPORTANTE! ; sum = 0 ; imprime la pila desde ebp-8 hasta ebp+16 ; ecx es i en el pseudoc odigo

4.7. INTERFAZANDO ENSAMBLADOR CON C Sumar enteros hasta: 10 Stack Dump # 1 EBP = BFFFFB70 ESP = BFFFFB68 +16 BFFFFB80 080499EC +12 BFFFFB7C BFFFFB80 +8 BFFFFB78 0000000A +4 BFFFFB74 08048501 +0 BFFFFB70 BFFFFB88 -4 BFFFFB6C 00000000 -8 BFFFFB68 4010648C Sum is 55

89

Figura 4.13: Muestra de la ejecuci on del programa sub5


28 29 30 31 32 33 34 35 36 37 38 39 40 41 42

cmp jnle add inc jmp end_for: mov mov mov pop leave ret

ecx, [ebp+8] end_for [ebp-4], ecx ecx short for_loop

; cmp i y n ; si no i <= n, sale ; sum += i

ebx, [ebp+12] eax, [ebp-4] [ebx], eax ebx

; ebx = sump ; eax = sum

; restaura ebx sub5.asm

Por qu e la l nea 22 de sub5.asm es importante? Porque la convenci on de llamado de C requiere que el valor de EBX no se modique por la funci on llamada. Si esto no se hace muy probable que el programa no trabajar a correctamente. La l nea 25 demuestra como trabaja el macro dump stack. Recuerde que el primer par ametro es s olo una etiqueta num erica, y el segundo y tercero determinan cu antas palabras dobles se muestran antes y despu es de EBP respectivamente. La Figura 4.13 muestra un ejemplo de la ejecuci on de un programa. Para este volcado, uno puede ver que la direcci on de la palabra doble para almacenar la suma es BB80 (en EBP + 12); el n umero a sumar es 0000000A (en EBP + 8); la direcci on de retorno para la rutina es

90

CAP ITULO 4. SUBPROGRAMAS

08048501 (en EBP + 4); el valor guardado de EBP es BFFFFB88 (en EBP); el valor de la variable local es 0 en (EBP- 4); y nalmente el valor guardado de EBX es 4010648C (en EBP- 8). La funci on calc sum podr a ser rescrita para devolver la suma como un valor de retorno en lugar de usar un par ametro apuntador. Ya que la suma es un valor entero, la suma se podr a dejar en el registro EAX. La l nea 11 del archivo main5.c deber a ser cambiada por: sum = calc sum(n); Tambi en, el prototipo de calc sum podr a necesitar ser alterado. Abajo est a, el c odigo modicado en ensamblador. sub6.asm ; subroutina _calc_sum ; halla la suma de los enteros de 1 hasta n ; Par ametros: ; n - hasta d onde se suma (en [ebp + 8]) ; Valor de retorno: ; la suma ; pseudoc odigo en C: ; int calc_sum( int n ) ; { ; int i, sum = 0; ; for( i=1; i <= n; i++ ) ; sum += i; ; return sum; ; } segment .text global _calc_sum ; ; local variable: ; sum at [ebp-4] _calc_sum: enter 4,0 ; hace espacio en la pila para sum mov mov for_loop: cmp jnle add inc dword [ebp-4],0 ecx, 1 ecx, [ebp+8] end_for [ebp-4], ecx ecx ; sum = 0 ; ecx es i en el pseudoc odigo ; cmp i and n ; si no i <= n, sale ; sum += i

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30

4.8. SUBPROGRAMAS ENTRANTES Y RECURSIVOS


1 2 3 4 5 6 7 8 9 10 11

91

segment .data format db "%d", 0 segment .text ... lea eax, [ebp-16] push eax push dword format call _scanf add esp, 8 ...

Figura 4.14: Llamando scanf desde ensamblador jmp end_for: mov leave ret eax, [ebp-4] ; eax = sum short for_loop

31 32 33 34 35 36 37

sub6.asm

4.7.8.

Llamando funciones de C desde ensamblador

Una gran ventaja de interfasar C y ensamblador es que le permite al c odigo de ensamblador acceder a la gran biblioteca de C y usar funciones escritas por el usuario. Por ejemplo, si usted desea llamar la funci on scanf para leer un entero del teclado. La Figura 4.14 muestra el c odigo que hace esto. Un punto muy importante que recordar es que scanf sigue la convenci on de llamado de C normalizada. Esto signica que preserva los valores de los registros EBX, ESI y EDI; sin embargo los registros EAX, ECX y EDX se pueden modicar. De hecho EAX denitivamente ser a cambiado, ya que el contiene el valor de retorno del llamado a scanf. Para otros ejemplos del uso de interfaces con C, vea el c odigo en asm io.asm que fue usado para crear asm io.obj.

4.8.

Subprogramas entrantes y recursivos

Un programa reentrante debe satisfacer las siguientes propiedades:

92

CAP ITULO 4. SUBPROGRAMAS No debe modicar ninguna instrucci on. En un lenguaje de alto nivel esto podr a ser dif cil, pero en ensamblador no es dif cil para un programa intentar modicar su propio c odigo. Por ejemplo: mov add word [cs:$+7], 5 ax, 2 ; copia 5 en la palabra 7 bytes adelante ; la instrucci on anterio cambia 2 por 5

Este c odigo trabajar a en modo real, pero en sistemas operativos con modo protegido el segmento de c odigo est a marcado de solo lectura. Cuando se ejecute la primera l nea, el programa se abortar a en estos sistemas. Esto es una mala forma de programar por muchas razones. Es confuso, dif cil de mantener y no permite compartir el c odigo (vea despu es). No debe modicar datos globales (tal como los datos que est an en el segmento data y bss). Todas las variables son almacenadas en la pila. Hay varias ventajas de escribir c odigo reentrante: Un subprograma reentrante se puede llamar recursivamente. Un programa reentrante puede compartirse en m ultiples procesos. En muchos sistemas operativos multitarea, si hay varias instancias de un programa ejecut andose, solo una copia del c odigo est a en memoria. Las bibliotecas compartidas y DLL (Dinamic Link Libraries ) usan esta idea tambi en. Programas reentrantes trabajan mucho mejor en programas multihilo 5 Windows 9x/NT y la mayor a de los sistemas operativos tipo UNIX (Solaris, Linux, etc.) soportan programas multihilo.

4.8.1.

Subprogramas recursivos

Estos tipos de subprogramas se invocan as mismos. La recursi on puede ser directa o indirecta. La recursi on directa ocurre cuando un subprograma, digamos foo se invoca as mismo dentro del cuerpo de foo. La recursi on indirecta ocurre cuando un subprograma no se llama as mismo directamente, pero otro subprograma lo llama. Por ejemplo, el subprograma foo podr a llamar a bar y bar podr a llamar a foo. Los programas recursivos deben tener una condici on de terminaci on. Cuando esta condici on es verdadera, no se necesita hacer m as llamadas. Si la rutina recursiva no tiene una condici on de terminaci on o la condici on nunca se vuelve verdadera, la recursi on nunca termina (muy parecido a un bucle innito).

4.8. SUBPROGRAMAS ENTRANTES Y RECURSIVOS

93

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20

; finds n! segment .text global _fact _fact: enter 0,0 mov cmp jbe dec push call pop mul jmp term_cond: mov end_fact: leave ret eax, [ebp+8] eax, 1 term_cond eax eax _fact ecx dword [ebp+8] short end_fact eax, 1 ; eax = n ; si n <= 1, termina

; eax = fact(n-1) ; respuesta en eax ; edx:eax = eax * [ebp+8]

Figura 4.15: Funci on factorial recursiva

n=3 frame

n(3)
Return address

Saved EBP n(2) n=2 frame


Return address

Saved EBP n(1) n=1 frame


Return address

Saved EBP
Figura 4.16: Marco de la pila para la funci on factorial

94 void f ( int x ) { int i ; for ( i =0; i < x; i ++ ) { printf ( %d\n, i); f ( i ); } }

CAP ITULO 4. SUBPROGRAMAS

Figura 4.17: Otro ejemplo (versi on de C) La Figura 4.15 muestra una funci on que calcula el factorial recursivamente. Puede ser llamado desde C con: x = fact (3); / nd 3! /

La Figura 4.16 muestra c omo se ve la pila en el punto m as profundo del llamado de la funci on anterior. Las Figuras 4.17 y 4.18 muestran otro ejemplo recursivo m as complicado en C y en ensamblador respectivamente. Cu al es la salida para f(3)? Observe que la instrucci on ENTER crea un nuevo i en la pila para cada llamado recursivo. As cada instancia recursiva de f tiene su propia variable independiente i. Deniendo i como una palabra doble en el segmento data no trabajar a igual.

4.8.2.

Revisi on de tipos de variables seg un su alcance en C

C suministra varios tipos de almacenamiento para las variables. global Estas variables est an denidas fuera de cualquier funci on y est an almacenadas en lugares jos de memoria (en los segmentos data o bss) y existen desde el inicio hasta el nal del programa. Por defecto ellas pueden ser accedidas por cualquier funci on en el programa; sin embargo, si ellas est an declaradas como static, solo las funciones en el mismo m odulo pueden acceder a ellas (en t erminos de ensamblador la etiqueta es interna, no externa). static Estas son variables locales de una funci on que son declaradas static (desafortunadamente, C usa la palabra reservada static para dos prop ositos diferentes). Estas variables se almacenan tambi en en lugares jos de memoria (en data bss), pero solo se pueden acceder directamente en las funciones en donde ellas se denieron.
5 un programa multihilo tiene varios hilos de ejecuci on. Esto es el programa en s mismo es multitarea.

4.8. SUBPROGRAMAS ENTRANTES Y RECURSIVOS

95

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30

%define i ebp-4 %define x ebp+8 ; macros utiles segment .data format db "%d", 10, 0 ; 10 = \n segment .text global _f extern _printf _f: enter 4,0 ; toma espacio en la pila para i mov lp: mov cmp jnl push push call add push call pop inc jmp quit: leave ret eax, [i] eax, [x] quit eax format _printf esp, 8 dword [i] _f eax dword [i] short lp ; es i < x? dword [i], 0 ; i = 0

; llama printf

; llama f

; i++

Figura 4.18: Otro ejemplo (versi on de ensamblador)

96

CAP ITULO 4. SUBPROGRAMAS

automatic Este es el tipo por defecto para una variable denida dentro de una funci on. Estas variables son colocadas en la pila cuando la funci on en la cual est an denidas se invocada y quitadas cuando la funci on retorna. As , ellas no tienen un lugar jo en memoria. register Esta palabra reservada le pregunta al compilador usar un registro para el dato de esta variable. Esto es solo una solicitud. El compilador no tiene que hacerlo. Si la direcci on de la variable se usa en cualquier parte del programa la variable no ser a de este tipo (ya que los registros no tienen direcci on). Tambi en, solo tipos enteros pueden ser valores tipo register. Tipos estructurados no lo pueden ser, ellos no caben en un registro! Los compiladores de C a menudo convertir an variables normales automatic en variables register sin ning un aviso al programador. volatile Esta palabra clave le dice al compilador que el valor en la variable puede cambiar en cualquier momento. Esto signica que el compilador no puede hacer ninguna suposici on sobre cu ando se modica la variable. 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 c odigo. No se pueden hacer este tipo de optimizaciones con variables volatile. Un ejemplo com un de una variable volatile podr a ser una que se alterara por dos hilos de un programa multihilo. Considere el siguiente c odigo: x = 10; y = 20; z = x; Si x pudiera ser alterada por otro hilo, es posible que otro hilo cambie x entre las l neas 1 y 3 as que z podr a no ser 10. Sin embargo, si x no fue declarada volatile, el compilador podr a asumir que x no cambia y ja z a 10. If x could be altered by another thread, it is possible that the other thread changes x between lines 1 and 3 so that z would not be 10. However, if the x was not declared volatile, the compiler might assume that x is unchanged and set z to 10. Otro uso de volatile es evitar que el compilador use un registro para una variable.

Cap tulo 5

Arreglos
5.1. Introducci on

Un arreglo es un bloque contiguo de una lista de datos en la memoria. Cada elemento de la lista debe ser del mismo tipo y usar exactamente el mismo n umero de bytes de memria para almacenarlo. Por estas propiedade, los arreglos permiten un acceso eciente de los datos por su posici on (o ndice) en el arreglo. La direcci on de cualquier elemento se puede calcular conociendo tres factores: La direcci on del primer elemento del arreglo. El n umero de bytes de cada elemento. El ndice del elemento. Es conveniente considerar el ndice del primer elemento como 0 (tal como C). Es posible usar otros valores para el primer ndic, pero esto complica los c alculos.

5.1.1.

Denir arreglos

Denir arreglos en los segmentos data y bss Para denir un arreglo iniciado en el segmento data, use las directivas normaleas: db, dw, etc. NASM tambi en suministra la directiva llamada TIMES que se puede usar para repetir una instrucci on muchas veces sin tener que duplicar las instrucciones a mano. La Figura 5.1 muestra varios ejemplos de estos. 97

98
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17

CAP ITULO 5. ARREGLOS

segment .data ; define un arreglo de 10 plabras dobles con valores ; iniciales de 1,2,..,10 a1 dd 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ; define un arreglo de 10 palabras inicadas todas con 0 a2 dw 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ; lo mismo de antes usando TIMES a3 times 10 dw 0 ; define un arreglo de bytes con ceros y luego 100 con unos a4 times 200 db 0 times 100 db 1 segment .bss ; define un arreglo de 10 palabras dobles sin valor inicial a5 resd 10 ; define un arreglo de 100 palabras dobles sin valor inicial a6 resw 100

Figura 5.1: Denir arreglos

Para denir un arreglo iniciado en el segmento bss, use las directivas resb, resw, etc. Recuerde que estas directivas tienen un operando que espera cu antas unidades de memoria reservar. La Figura 5.1 muestra tambi en ejemplos de este tipo de deniciones

Denir arreglos como variables locales en la pila No hay una manera directa de denir una arreglo como variable local en la pila. Como antes, uno calcula el el total de bytes necesarios para todas las variables locales, incluidos los arreglos, y restar esto de ESP (o directamente usando la instrucci on ENTER). Por ejemplo, si una funci on necesita una variable caracter, dos enteros y 50 elementos de un arreglo de palabras, uno necesitar a 1 + 2 + 50 = 109 bytes. Sin embargo, el n umero restado de ESP deber a ser un m ultiplo de cuatro (112 en este caso) para que ESP est e en el boundary de pna palabra doble. Uno podr a formar las variables dentro de los 10g bytes de varias maneras. La Figura 5.2 muestra dos maneras posibles. 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.

5.1. INTRODUCCION EBP - 1 EBP - 8 EBP - 12 char no usado dword 1 dword 2

99

word array

word array

EBP EBP EBP EBP

100 104 108 109

EBP - 112 Figura 5.2: Disposici on de la pila

dword 1 dword 2 char no usado

5.1.2.

Acdeder a elementos de los arreglos

No hay operador [ ] en ensamblador como en C. Para acceder a un elemento del arreglo, se debe calcular la direcci on. Considere las dos deniciones siguientes de arreglos: array1 array2 db dw 5, 4, 3, 2, 1 5, 4, 3, 2, 1 ; arreglo de bytes ; arreglo de palabras

A continuaci on algunos ejemplos usando estos arreglos.


1 2 3 4 5 6 7

mov mov mov mov mov mov mov

al, [array1] al, [array1 + [array1 + 3], ax, [array2] ax, [array2 + [array2 + 6], ax, [array2 +

1] al 2] ax 1]

; ; ; ; ; ; ;

al = array1[0] al = array1[1] array1[3] = al ax = array2[0] ax = array2[1] (NO array2[2]!) array2[3] = ax ax = ??

En la l nea 5, se referencia el elemento 1 del arreglo, no el segundo. Por qu e? Las palabras son unidades de 2 bytes, para moverse al siguiente elemento del arreglo uno se debe mover 2 bytes adelante, no uno. La l nea 7 leer a un byte del primer elemento y uno del segundo. En C, el compilador mira el tipo de apuntador para determinar cu antos bytes mover en una expresi on aritm etica para que el programador no tenga que hacerla. Sin embargo el ensamblador es el que calcula el tama no de los elementos del arreglo cuando se mueve de elemento en elemento. La Figura 5.3 muestra un fragmento de c odigo que suma todos los elementos de array1 del ejemplo anterior. En la l nea 7, a AX se le suma DX. Por qu e no AL? Primero, los dos operandos de la instrucci on ADD deben

100
1 2 3 4 5 6 7 8 9

CAP ITULO 5. ARREGLOS ebx, array1 dx, 0 ah, 0 ecx, 5 al, [ebx] dx, ax ebx lp ; ebx = direcci on de array1 ; dx almacenar a sum ; ?

mov mov mov mov lp: mov add inc loop

; al = *ebx ; dx += ax (not al!) ; bx++

Figura 5.3: Sumar elementos de un arreglo (Versi on 1)

1 2 3 4 5 6 7 8 9 10

mov mov mov lp: add jnc inc next: inc loop

ebx, array1 dx, 0 ecx, 5 dl, [ebx] next dh ebx lp

; ebx = direcci on de array1 ; dx almacenar a sum

; dl += *ebx ; if no hay carry vaya a next ; inc dh ; bx++

Figura 5.4: Sumar elementos de un arreglo (Versi on 2)

ser del mismo tama no. Segundo, deber a ser f acil sumar bytes y llegar a un resultado que no quepa en un byte. Usando DX, se permite una suma hasta 65.535. Sin embargo es importante ver que se est a sumando AH tambi en. Esta es la raz on por la cual se ja AH a cero. 1 en la l nea 3. also. This is why AH is set to zero2 in line 3. Las Figuras 5.4 y 5.5 muestran dos alternativas para calcular la suma. Las l neas en it alica reemplazan las l neas 6 y 7 de la Figura 5.3.

Fijando AH a cero se asume impl citamente que AL es un n umero sin signo. Si es con signo, la acci on apropiada ser a insertar una instrucci on CBW entre las l neas 6 y 7 2 Setting AH to zero is implicitly assuming that AL is an unsigned number. If it is signed, the appropriate action would be to insert a CBW instruction between lines 6 and 7

5.1. INTRODUCCION
1 2 3 4 5 6 7 8

101 ; ebx = direcci on de ; dx almacenar a sum array1

mov mov mov lp: add adc inc loop

ebx, array1 dx, 0 ecx, 5 dl, [ebx] dh, 0 ebx lp

; dl += *ebx ; dh += bandera de carry + 0 ; bx++

Figura 5.5: Sumar elementos de un arreglo (Versi on 3)

5.1.3.

Direccionamiento indirecto m as avanzado

No es sorprendente que se use direccionamiento indirecto con arreglos. La forma m as general de una referencia indirecta memoria es [ reg base + factor *reg ndice + constante ] donde: reg base es uno de los registros EAX, EBX, ECX, EDX, EBP, ESP, ESI o EDI. factor es 1, 2, 4 o 8. (Si 1, el factor se omite) index reg es uno de estos registros EAX, EBX, ECX, EDX, EBP, ESI, EDI. (observe que ESP no est a en la lista.) constant es una constante de 32 bits. La constante puede ser una etiqueta (o expresi on de etiqueta).

5.1.4.

Ejemplo

Se presenta un ejemplo que usa un arreglo y se pasa a la funci on. Usa el programa array1c.c (listado abajo) como driver y no el programa driver.ci.
1 2 3 4 5 6

%define ARRAY_SIZE 100 %define NEW_LINE 10 segment .data FirstMsg Prompt

array1.asm

db db

"Primeros 10 elementos del arreglo", 0 "Ingrese el ndice del elemento a mostrar: ", 0

102
7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48

CAP ITULO 5. ARREGLOS db db db "Elemento %d es %d", NEW_LINE, 0 "Elementos 20 hasta 29 del arreglo", 0 "%d", 0

SecondMsg ThirdMsg InputFormat segment .bss array segment .text extern global _asm_main: enter push push

resd ARRAY_SIZE

_puts, _printf, _scanf, _dump_line _asm_main 4,0 ebx esi ; variable local en EBP - 4

; Se inicia el arreglo con 100, 99, 98, 97, ... mov mov init_loop: mov add loop push call pop push push call add ecx, ARRAY_SIZE ebx, array [ebx], ecx ebx, 4 init_loop dword FirstMsg _puts ecx dword 10 dword array _print_array esp, 8 ; imprime FirstMsg

; imprime los 10 primeros elementos del arreglo

; Le pregunta al usuario el ndice del elemento Prompt_loop: push dword Prompt call _printf pop ecx lea push push eax, [ebp-4] ; eax = direcci on de la variable local eax dword InputFormat

5.1. INTRODUCCION
49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90

103

call add cmp je call jmp InputOK: mov push push push call add push call pop push push call add pop pop mov leave ret ; ; ; ; ; ; ; ; ;

_scanf esp, 8 eax, 1 InputOK

; eax = valor de retorno de scanf

_dump_line ; dump rest of line and start over Prompt_loop ; si la entrada no es v alida

esi, [ebp-4] dword [array + 4*esi] esi dword SecondMsg ; imprime el valor del elemento _printf esp, 12 dword ThirdMsg _puts ecx dword 10 dword array + 20*4 _print_array esp, 8 esi ebx eax, 0 ; imprime los elementos 20 a 29

; direcci on de array[20]

; retorna a C

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, int n); Par ametros: a - Apuntador al arreglo a imprimir (en ebp+8 en la pila) n - n umero de enteros a imprimir (en ebp+12 en la pila)

segment .data

104
91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119

CAP ITULO 5. ARREGLOS db "%-5d %5d", NEW_LINE, 0

OutputFormat segment .text global _print_array: enter push push xor mov mov print_loop: push push push push call add inc pop loop pop pop leave ret

_print_array 0,0 esi ebx esi, esi ecx, [ebp+12] ebx, [ebp+8] ecx dword [ebx + 4*esi] esi dword OutputFormat _printf esp, 12 esi ecx print_loop ebx esi array1.asm array1c.c ; esi = 0 ; ecx = n ; ebx = direcci on del arreglo ; <print podr a cambiar ecx! ; empuja array[esi]

; quita los par ametros (<deja ecx!)

#include <stdio.h> int asm main( void ); void dump line( void ); int main() { int ret status ; ret status = asm main(); return ret status ;

5.1. INTRODUCCION } / o funcin dump line dumps all chars left in current line from input buer / void dump line() { int ch; while ( (ch = getchar()) != EOF && ch != \n) / cuerpo vacio / ; } array1c.c

105

Revisi on de la instrucci on LEA La instrucci on LEA se puede usar para otros prop ositos que s olo calcular direcciones. Un uso com un es para c alculos r apidos. Considere lo siguiente: lea ebx, [4*eax + eax]

Esto efectivamente almacena el valor de 5 EAX en EBX. Usando LEA para hacer esto, es m as f acil y r apido que usar MUL. Sin embargo, uno debe tener en cuenta que la expresi on dentro de los par entesis cuadrados debe ser una direcci on indirecta correcta. As por ejemplo, esta instrucci on no se puede usar para multiplicar por 6 r apidamente.

5.1.5.

Arreglos multidimiensionales

Un arreglo multidimensional no es realmente muy diferente que los arreglos unidimensionales ya discutidos de hecho, est an representados en memoria tal como un arreglo unidimensional. Arreglos bidimensionales No debe sorprender que el arreglo multidimiensional m as elemental es el arreglo unidimensional ya discutido. Un arreglo multidimensional se presenta a menudo como una malla de elementos. Cada elemento est a identicado por un par de ndices. Por convenci on, el primer ndice es identicado con la la del elemento y el segundo ndice la columna. Considere un arreglo con tres las y dos columnas denidas como:

106
1 2 3 4 5

CAP ITULO 5. ARREGLOS eax, eax, eax, eax, [ebp [ebp 1 [ebp [ebp + - 52], 44] 48] 4*eax - 40] eax ; ; ; ; ; ebp - 44 es el sitio de i multiplica i por 2 add j ebp - 40 es la direcci on a[0][0] Almacena el resultado en ebp - 52

mov sal add mov mov

Figura 5.6: Ensamblador para x = a[ i ][ j ] int a [3][2]; El compilador de C reservar a espacio para un arreglo de 6 (= 2 3) enteros y ordena los elementos como sigue: Indice Elemento 0 a[0][0] 1 a[0][1] 2 a[1][0] 3 a[1][1] 4 a[2][0] 5 a[2][1]

La tabla intenta mostrar c omo el elemento referenciado como a[0][0] es almacenado en el comienzo de los 6 elementos de un arreglo unidimensional. El elemento a [0][l] se almacena en la siguiente posici on ( ndice 1) y as sucesivamente. Cada la del arreglo bidimensional se almacena contiguamente en memoria. El u ltimo elemento de una la es seguido por el primer elemento de la pr oxima la. Esto es conocido como la representaci on rowwise del arreglo y es como un compilador de C/C++ podr a representar el arreglo. C omo el compilador determina d onde aparece a[i][j] en la representaci on rowwise? Una f ormula elemental calcular a el ndice de i y j. La f ormula en este caso es 2i + j . No es dif cil ver c omo se deriva esta f ormula. Cada la es de dos elementos; as , el primer elemento de la la i est a en la posici on 2i . Entonces la posici on de la columna j se encuentra sumando j a 2i. Este an alisis tambi en muestra c omo la f ormula se generaliza para un arreglo con N columnas. N i + j . Observe que la f ormula no depende del n umero de las. Como un ejemplo, veamos c omo compila gcc el siguiente c odigo (usando el arreglo a denido antes): x = a[ i ][ j ]; La Figura 5.6 muestra c omo el ensamblador trabaja esto, as , el compilador esencialmente convierte el c odigo a: x = (&a[0][0] + 2 i + j ); y de hecho, el programador podr a escribir de esta manera con los mismos resultados.

5.1. INTRODUCCION

107

No hay nada m agico sobre la escogencia de la representaci on rowwise del arreglo. Una representaci on columnwise podr a trabajar bien: Index Element 0 a[0][0] 1 a[1][0] 2 a[2][0] 3 a[0][1] 4 a[1][1] 5 a[2][1]

En la representaci on columnwise, cada columna se almacena contigua. El elemento [i][j] se almacenan en la posici on i + 3j . Otros lenguajes (FORTRAN, por ejemplo) usan la representaci on columnwise. Esto es importante cuando se interfaza el c odigo con varios lenguajes. Dimensiones mayores que dos Para las dimensiones mayores de 2, se aplica la misma idea b asica. Considere el arreglo tridimensional: int b [4][3][2]; Este arreglo deber a ser almacenado como si fueran cuatro arreglos bidimensionales cada uno de tama no [3][2] consecutivamente en memoria. La tabla de abajo muestra como comienza el: Index Element Index Element 0 b[0][0][0] 6 b[1][0][0] 1 b[0][0][1] 7 b[1][0][1] 2 b[0][1][0] 8 b[1][1][0] 3 b[0][1][1] 9 b[1][1][1] 4 b[0][2][0] 10 b[1][2][0] 5 b[0][2][1] 11 b[1][2][1]

La f ormula para calcular la posici on de b[i][j][k] es 6i + 2j + k . El 6 est a determinado por el tama no de los arreglos [3][2]. En general, para un arreglo a[i][j][k] ser a: M N i + N j + k . Observe que nuevamente la primera dimensi on L no aparece en la f ormula. Para dimensiones mayores, se generaliza el mismo proceso. Para un arreglo n dimensional de dimensiones D1 a Dn , la posici on del elemento denotada por los ndices i1 a in est a dada por la f ormula: D2 D3 Dn i1 + D3 D4 Dn i2 + + Dn in1 + in o puede ser escrito m as corto como:
n j =1

Dk ij
Aqu es donde puede decirle al autor que fue un mejor f sico (o fue la referencia a FORTRAN un regalo?)

k=j +1

La primera dimensi on D1 no aparece en la f ormula: Por una representaci on columnwise la f ormula general ser a: i1 + D1 i2 + + D1 D2 Dn2 in1 + D1 D2 Dn1 in

108 En la notaci on matem atica griega de u ber:


n j =1

CAP ITULO 5. ARREGLOS

j 1 k=1

Dk ij

En este caso, es la u ltima dimensi on Dn , la que no aparece en la f ormula. Pasar arreglos multidimensionales como par ametros en C La representaci on rowwise de arreglos multidimensionales tiene un efecto directo en la programaci on en C. Para arreglos unidimensionales, no no se necesita el tama no del arreglo para calcular donde est a localizado un elemento en memoria. Esto no es cierto para arreglos multidimensionales. Acceder a los elementos de estos arreglos el compilador debe conocer todo menos la primera dimensi on. Esto es aparente cuando consideramos el prototipo de la funci on que toma un arreglo multidimensional como par ametro. Lo siguiente no compilar a: void f ( int a [ ][ ] ); / No hay oinformacin de la odimensin /

Sin embargo, lo siguiente compila: void f ( int a [ ][2] ); Cualquier arreglo bidimensional con dos columnas puede ser pasado a esta funci on. La primera dimensi on no se necesita. 3 No se confunda con una funci on con este prototipo: void f ( int a [ ] ); Esto dene 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). Para arreglos de dimensiones mayores, se debe especicar todo menos la primera dimensi on. Por ejemplo, un arreglo de 4 dimensiones se podr a pasar como: void f ( int a [ ][4][3][2] );

5.2.

Instrucciones de arreglos/cadenas

La familia de procesadores 80X86 suministran varias instrucciones que est an dise nadas para trabajar con arreglos. Estas instrucciones son llamadas instrucciones de cadena. Ellas usan los registros de ndice (ESI y EDI) para
3

Se puede especicar un tama no all , pero ser a ignorado por el compilador.

5.2. INSTRUCCIONES DE ARREGLOS/CADENAS LODSB LODSW LODSD AL = [DS:ESI] ESI = ESI 1 AX = [DS:ESI] ESI = ESI 2 EAX = [DS:ESI] ESI = ESI 4 STOSB STOSW STOSD [ES:EDI] = AL EDI = EDI 1 [ES:EDI] = AX EDI = EDI 2 [ES:EDI] = EAX EDI = EDI 4

109

Figura 5.7: Instrucciones de cadena de lectura y escritura realizar una operaci on y entonces autom aticamente incrementan o decrementan uno o los dos registros de ndice. La bandera direcci on (DF) en el registro FLAGS determina d onde los registros de ndice son incrementados o decrementados. Hay dos instrucciones que modican la bandera direcci on: CLD Borra la bandera de direcci on. En este estado, los registros de ndice se incrementan. STD Establece la bandera de direcci on. En este estado los registros de ndice se decrementan. Un error muy com un en la programaci on de 80X86 es olvidar colocar la bandera de direcci on en el estado correcto de manera expl cita. Esto a menudo hace que el c odigo trabaje la mayor parte del tiempo (cuando sucede que la bandera de direcci on est a en el estado deseado) pero no trabaja todo el tiempo.

5.2.1.

Leer y escribir en la memoria

Las instrucciones m as elementales leen o escriben en memoria. Ellas pueden leer o escribir un byte, palabra o palabra doble de una vez. La Figura 5.7 muestra estas instrucciones con una corta descripci on en pseudoc odigo qu e hace ella. Hay varios puntos para tener en cuenta. Primero, ESI se usa para leer y EDI para escribir. Es f acil recordar esto si uno tiene en cuenta que SI signica Source Index y DI signica Destination Index. Ahora observe que el registro que almacena el dato es jo (AL, AX o AEX). Finalmente, observe que las instrucciones de almacenamiento usan ES para determinar el segmento donde escribir, no DS. En la programaci on en modo protegido esto no es normalmente un problema, ya que hay s olo un segmento de datos y ES deber a ser iniciado autom aticamente para referenciarlo. (tal como es DS). Sin embargo, en la programaci on en modo real es muy importante para el programador iniciar ES con el valor de selector del segmento correcto. 4 La Figura 5.8 muestra un ejemplo del uso de estas instrucciones
4 Otra complicaci on es que uno no puede copiar el valor de DS en el registro de ES directamente usando la instrucci on MOV. En lugar de ello, el valor DS debe copiarse a un

110
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15

CAP ITULO 5. ARREGLOS

segment .data array1 dd 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 segment .bss array2 resd 10 segment .text cld mov esi, array1 mov edi, array2 mov ecx, 10 lp: lodsd stosd loop lp

; <No olvide esto!

Figura 5.8: Ejemplo de carga y almacenamiento que copia un arreglo en otro. La combinaci on de una instrucci on LODSx y STOSx (en las l neas 13 y 14 de la Figura 5.8) es muy com un. De hecho, esta combinaci on se puede realizar con una sola instrucci on para cadenas MOVSx. La Figura 5.9 describe las operaciones que estas instrucciones realizan. Las l neas 13 y 14 de la Figura 5.8 deber a ser reemplazada con una sola instrucci on MOVSD con el mismo efecto. La u nica diferencia ser a que el registro EAX no ser a usado en el bucle.

5.2.2.

El prejo de instrucci on REP

La familia 80X86 suministra un prejo de instrucci on especial 5 llamado REP que se puede usar con las instrucciones anteriores Este prejo le dice a la CPU que repita la pr oxima instrucci on de cadena un n umero especicado de veces. El registro ECX se usa para contar las iteraciones (tal como la instrucci on LOOP). Usando el prejo REP, el bucle en la Figura 5.8 (l neas 12 a 15) se podr a reemplazar con una sola l nea: rep movsd
registro de prop osito general (como AX) y entonces copiado de este registro a ES usando dos instrucciones MOV. 5 un prejo de instrucci on no es una instrucci on, es un byte especial que se coloca antes de una instrucci on de cadena que modica el comportamiento de la instrucci on. Se usan otros prejos para sobreescribir los segmentos por defecto de los accesos a memoria

5.2. INSTRUCCIONES DE ARREGLOS/CADENAS MOVSB byte [ES:EDI] = byte [DS:ESI] ESI = ESI 1 EDI = EDI 1 word [ES:EDI] = word [DS:ESI] ESI = ESI 2 EDI = EDI 2 dword [ES:EDI] = dword [DS:ESI] ESI = ESI 4 EDI = EDI 4

111

MOVSW

MOVSD

Figura 5.9: Instrucciones de cadena de movimiento de memoria


1 2 3 4 5 6 7 8 9

segment .bss array resd 10 segment .text cld mov edi, array mov ecx, 10 xor eax, eax rep stosd

; <No olvide esto!

Figura 5.10: Ejemplo de arreglo cero La Figura 5.10 muestra otro ejemplo que llena de ceros el contenido de un arreglo.

5.2.3.

Comparaci on de las instrucciones de cadena

La Figura 5.11 muestra varias instrucciones de cadena nuevas que se pueden usar para comparar memoria con otra memoria o un registro. Ellas son u tiles para comparar o buscar en arreglos. Ellas jan el registro FLAGS tal como la instrucci on CMP. Las instrucciones CMPSx comparan el lugar de memoria correspondiente y SCASx comparan lugares de memoria en busca de un valor espec co. La Figura 5.12 muestra una porci on de c odigo que busca el n umero 12 en un arreglo de palabras dobles, la instrucci on SCASD en la l nea 10 siempre le suma 4 a EDI a un si el valor buscado se encuentra. As si uno desea encontrar la direcci on de donde est a el 12 es necesario sustraer 4 de EDI (como lo hace la l nea 16).

112 CMPSB

CAP ITULO 5. ARREGLOS compara el byte [DS:ESI] y el byte [ES:EDI] ESI = ESI 1 EDI = EDI 1 compara la palabra [DS:ESI] y la word [ES:EDI] ESI = ESI 2 EDI = EDI 2 compara la dword [DS:ESI] y la dword [ES:EDI] ESI = ESI 4 EDI = EDI 4 compara AL y [ES:EDI] EDI 1 compara AX y [ES:EDI] EDI 2 compara EAX y [ES:EDI] EDI 4

CMPSW

CMPSD

SCASB SCASW SCASD

Figura 5.11: Comparaci on de las instrucciones de cadena

5.2.4.

Prejos de instrucci on REPx

Hay varios prejos de instrucci on del tipo REP que se pueden usar con las instrucciones de comparaci on de cadenas. La Figura 5.13 muestra los dos nuevos prejos y describe su operaci on. REPE y REPZ son sin onimos para el mismo prejo (como RPNE y REPNZ) si la comparaci on repetida de la instrucci on de cadena se detiene por el resultado de la comparaci on, el registro o registros ndice se incrementan y ECX se decrementa; Sin embargo, Por qu e uno no solo ve el registro FLAGS almacenar a el estado que termin o la repetici on. si ECX es cero despu es de As es posible usar la bandera Z para determinar si las comparaciones repetir la comparaci on? repetidas pararon por una comparaci on o porque ECX es cero. La Figura 5.14 muestra una porci on c odigo de ejemplo que determina si dos bloques de memoria son iguales. El JE en la l nea 7 del ejemplo examina el resultado de la instrucci on anterior. Si la comparaci on repetida para porque encontr o dos bytes diferentes, la bandera Z continuar a borrada y no se hace el salto; sin embargo, si las comparaciones paran porque se vuelve cero, la bandera Z estar a jada y el c odigo salta a la etiqueta equal.

5.2.5.

Ejemplo

Esta secci on contiene un archivo fuente en ensamblador con varias funciones que implementan operaciones con arreglos usando instrucciones de cadena. Muchas de las funciones duplican las funciones de las bibliotecas

5.2. INSTRUCCIONES DE ARREGLOS/CADENAS


1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18

113

segment .bss array resd 100 segment .text cld mov edi, array ; apuntador al inicio del arreglo mov ecx, 100 ; n umero de elementos mov eax, 12 ; n umero a buscar lp: scasd je found loop lp ; c odigo a ejecutar si no se encontr o jmp onward found: sub edi, 4 ; edi apunta ahora a 12 en array ; c odigo a ejecutar si se encontr o onward:

Figura 5.12: Ejemplo de b usqueda REPE, REPZ REPNE, REPNZ repite la instrucci onn mientras la bandera Z est e ja o m aximo ECX veces repite la instrucci on mientras la bandera Z est e borrada o m aximo ECX veces Figura 5.13: Prejos de instrucci on REPx conocidas de C. memory.asm global _asm_copy, _asm_find, _asm_strlen, _asm_strcpy segment .text ; funci on _asm_copy ; copia un bloque de memoria ; prototipo de C ; void asm_copy( void * dest, const void * src, unsigned sz); ; par ametros: ; dest - apuntador del b ufer para copiar a ; src - apuntador del b ufer para copiar desde ; sz - n umero de bytes a copiar

1 2 3 4 5 6 7 8 9 10 11

114
1 2 3 4 5 6 7 8 9 10 11 12

CAP ITULO 5. ARREGLOS

segment .text cld mov esi, block1 ; direcci on del primer bloque mov edi, block2 ; direcci on del segundo bloque mov ecx, size ; tama~ no del bloque en bytes repe cmpsb ; repita mientras la bandera Z est e fija je equal ; Si Z est a fija, los bloque son iguales ; c odigo para ejecutar si los bloques no son igual jmp onward equal: ; c odigo para ejecutar si los bloques son iguales onward:

Figura 5.14: Comparando bloques de memoria

12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36

; ahora, se definen algunos s mbolos utiles %define dest [ebp+8] %define src [ebp+12] %define sz [ebp+16] _asm_copy: enter 0, 0 push esi push edi mov mov mov cld rep pop pop leave ret esi, src edi, dest ecx, sz ; esi = direcci on del b ufer para copiar desde ; edi = direcci on del b ufer para copia a ; ecx = n umero de bytes a copiar ; borrar la bandera de direcci on ; ejecute movsb ECX veces

movsb edi esi

; function _asm_find

5.2. INSTRUCCIONES DE ARREGLOS/CADENAS


37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78

115

; Busca en la memoria un byte dado ; void * asm_find( const void * src, char target, unsigned sz); ; par ametros ; src - apuntador al lugar d onde buscar ; target - valor del byte a buscar ; sz - tama~ no en bytes de d onde se busca ; valor de retorno ; si target se encuentra, apunta a la primera ocurrencia de target ; ; si no ; retorna NULL ; OBSERVE: target es un valor de un byte, pero se empuja en la pila como ; una palabra doble. El byte se almacena en los 8 bits inferiores. ; %define src [ebp+8] %define target [ebp+12] %define sz [ebp+16] _asm_find: enter push mov mov mov cld repne je mov jmp found_it: mov dec quit: pop leave ret

0,0 edi eax, target edi, src ecx, sz ; al tiene el valor a buscar

scasb found_it eax, 0 short quit eax, edi eax edi

; busca hasta que ECX == 0 or [ES:EDI] == AL ; Si la bandera cero se fija, entonces se ; encontr o el valor ; Si no se encontr o, retorna un apuntador NULL

; Si se encuentra retorna (DI - 1)

116
79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120

CAP ITULO 5. ARREGLOS

; ; ; ; ; ; ;

function _asm_strlen retorna el tama~ no de una cadena unsigned asm_strlen( const char * ); par ametro src - apuntador a la cadena valor de retorno: n umero de caracteres en la cadena (sin contar el 0 final) (en EAX)

%define src [ebp + 8] _asm_strlen: enter 0,0 push edi mov mov xor cld repnz edi, src ; edi = apuntador a la cadena ecx, 0FFFFFFFFh ; usa el mayor valor posible en ECX al,al ; al = 0

scasb

; busca un 0 para terminar

; ; repnz will go one step too far, so length is FFFFFFFE - ECX, ; not FFFFFFFF - ECX ; mov eax,0FFFFFFFEh sub eax, ecx ; length = 0FFFFFFFEh - ecx pop leave ret edi

; funci on _asm_strcpy ; copia una cadena ; void asm_strcpy( char * dest, const char * src); ; par ametros ; dest - apuntador a la cadena de destion ; src - apuntador a la cadena origen ; %define dest [ebp + 8] %define src [ebp + 12] _asm_strcpy: enter 0,0

5.2. INSTRUCCIONES DE ARREGLOS/CADENAS


121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136

117

push push mov mov cld cpy_loop: lodsb stosb or jnz pop pop leave ret

esi edi edi, dest esi, src

al, al cpy_loop edi esi

; ; ; ;

Caraga AL e inc si almacena AL e inc di fija las banderas de condici on si no pasa terminando 0, contin ua

memory.asm memex.c

#include <stdio.h> #dene STR SIZE 30 / prototipos / void asm copy( void , const void , unsigned ) attribute (( cdecl )); void asm nd( const void , char target , unsigned ) attribute (( cdecl )); unsigned asm strlen( const char ) attribute (( cdecl )); void asm strcpy( char , const char ) attribute (( cdecl )); int main() { char st1 [STR SIZE] = test string; char st2 [STR SIZE]; char st ; char ch; asm copy(st2, st1 , STR SIZE); / copia todos los 30 caracteres de la cadena/ printf ( %s\n, st2); printf (Digite un caracter : ); / busca un byte en la cadena /

118 scanf( %c %[\n], &ch); st = asm nd(st2 , ch , STR SIZE); if ( st ) printf (Encontrado en : %s\n, st ); else printf (No encontrado\n); st1 [0] = 0; printf (Digite una Cadena:); scanf( %s, st1); printf (len = %u\n, asm strlen(st1 )); asm strcpy( st2 , st1 ); printf ( %s\n, st2 ); return 0; } memex.c

CAP ITULO 5. ARREGLOS

/ copy meaningful data in string /

Cap tulo 6

Punto otante
6.1.
6.1.1.

Representaci on de punto otante


N umeros binarios no enteros

Cuando se discutieron los sistemas num ericos en el primer cap tulo, s olo se trataron los valores enteros. Obviamente, debe ser posible representar n umeros no enteros en otras bases como en decimal. En decimal, los d gitos a la derecha del punto decimal tienen asociados potencias de 10 negativas. 0,123 = 1 101 + 2 102 + 3 103 No es sorprendente que los n umeros binarios trabajen parecido. 0,1012 = 1 21 + 0 22 + 1 23 = 0,625 Esta idea se puede combinar con los m etodos para enteros del cap tulo 1 para convertir un n umero. 110,0112 = 4 + 2 + 0,25 + 0,125 = 6,375 Convertir de decimal a binario no es muy dif cil. En general divida el n umero decimal en dos partes: entero y fracci on. Convierta la parte entera usando a binario usando los m etodos del cap tulo 1. La parte fraccionaria se convierte usando el m etodo descrito abajo: Considere una fracci on binaria con los bits etiquetados como a, b, c, . . . entonces el n umero se ve como: 0, abcdef . . . Multiplique el n umero por dos. La representaci on binaria del nuevo n umero ser a: a, bcdef . . . 119

120

CAP ITULO 6. PUNTO FLOTANTE

0,5625 2 = 1,125 0,125 2 = 0,25 0,25 2 = 0,5 0,5 2 = 1,0

rst bit = 1 second bit = 0 third bit = 0 fourth bit = 1

Figura 6.1: Convertir 0.5625 a binario

0,85 2 = 1,7 0,7 2 = 1,4 0,4 2 = 0,8 0,8 2 = 1,6 0,6 2 = 1,2 0,2 2 = 0,4 0,4 2 = 0,8 0,8 2 = 1,6

Figura 6.2: Convertir 0.85 a binario Observe que el primer bit est a ahora en el lugar del uno. Reemplace a con 0 y obtiene: 0, bcdef . . . y multiplique por dos otra vez para obtener: b, cdef . . . Ahora el segundo bit (b) est a en la posici on del uno. Este procedimiento se puede repetir hasta los bits que se deseen encontrar. La Figura 6.1 muestra un ejemplo real que convierte 0.5625 a binario. El m etodo para cuando la parte fraccionaria es cero. Como otro ejemplo, considere convertir 23.85 a binario. Es f acil convertir la parte entera (23 = 101112 ), pero Qu e pasa con la parte fraccionaria (0,85)? La Figura 6.2 muestra el comienzo de este c alculo. Si uno mira cuidadosamente los n umeros, se encuentra con un bucle innito. Esto signica que

DE PUNTO FLOTANTE 6.1. REPRESENTACION

121

0.85 en un binario peri odico (opuesto a los decimales peri odicos en base 10). 1 Hay un patr on de los n umeros calculados. Mirando en el patr on, uno puede 23,85 = 10111,1101102 . ver que 0,85 = 0,1101102 . As Una consecuencia importante del c alculo anterior es que 23.85 no se puede representar exactamente en binario usando un n umero nito de bits 1 (tal como 3 no se puede representar en decimal con un n umero nito de d gitos). Como muestra este cap tulo, las variables float y double en C son almacenados en binario. As , valores como 23.85 no se pueden almacenar exactamente en estas variables. S olo se puede almacenar una aproximaci on a 23.85 Para simplicar el hardware, los n umeros de puntos otante se almacenan con un formato consistente. Este formato usa la notaci on cient ca (pero en binario, usando potencias de dos, no de diez). Por ejemplo 23.85 o 10111,11011001100110 . . .2 se almacenar a como: 1,011111011001100110 . . . 2100 (Donde el exponente (100) est a en binario). Un n umero de punto otante normalizado tiene la forma: 1, ssssssssssssssss 2eeeeeee D onde 1, sssssssssssss es la mantisa y eeeeeeee es el exponente.

6.1.2.

Representaci on IEEE de punto otante

El IEEE (Institute of Electerical and Electronic Engineer) es una organizaci on internacional que ha dise nado formato binarios espec cos para almacenar n umeros de punto otante. Este formato se usa en la mayor a (pero no todos) los computadores hechos hoy d a. A menudo es soportado por el hardware de computador en s mismo. Por ejemplo el coprocesador num erico (o matem atico) de Intel (que est a empotrado en todas las CPU desde el Pentium) lo usa. El IEEE dene dos formatos con precisi on diferentes: precisi on simple y doble, la precisi on simple es usada por las variables float en C y la precisi on doble es usada por la variable double. El coprocesador matem atico de Intel utiliza un tercer nivel de mayor precisi on llamado precisi on extendida. De hecho, todos los datos en el coprocesador en s mismo est an en esta precisi on. Cuando se almacenan en memoria desde el coprocesador se convierte a precisi on simple o doble au2 tom aticamente. La precisi on extendida usa un formato general ligeramente
1 No deber a sorprenderse de que un n umero pudiera repetirse en una base, pero no en otra. Piense en 1 , es peri o dico en decimal, pero en ternario (base3) ser a 0,13 4. 3 2 Algunos compiladores (como Borland) los tipos long double usa esta precisi on extendida. Sin embargo, otros compiladores usan la precisi on doble para double y long double. (Esto es permitido por ANSI C).

122 31 30 s s e f 23 e 22

CAP ITULO 6. PUNTO FLOTANTE 0 f

bit de signo - 0 = positivo, 1 = negativo exponente polarizado (8-bits) = verdadero exponente + 7F (127 decimal). Los valores 00 y FF tienen signicado especial (vea el texto). fracci on - los 23 primeros bits despu es de 1. en la mantisa. Figura 6.3: Precisi on simple de la IEEE

diferente que los formatos oat y double de la IEEE y no ser an discutidos ac a. Presici on simple IEEE El punto otante de precisi on simple usa 32 bits para codicar el n umero. Normalmente son exactos los primeros 7 d gitos decimales. Los n umeros de punto otante son almacenados en una forma mucho m as complicada que los enteros. La Figura 6.3 muestra la forma b asica del formato de precisi on simple del IEEE. Hay varias peculiaridades del formato. Los n umeros de punto otante no usan la representaci on en complemento a 2 para los n umeros negativos. Ellos usan la representaci on de magnitud y signo. El bit 31 determina el signo del n umero como se muestra. El exponente binario no se almacena directamente. En su lugar, se almacena la suma del exponente y 7F en los bits 23 al 30. Este exponente polarizado siempre es no negativo. La parte fraccionaria se asume que es normalizada (en la forma 1.sssssssss). Ya que el primer bit es siempre uno, este uno no se almacena. Esto permite el almacenamiento de un bit adicional al nal y as se incrementa un poco la precisi on. Esta idea se conoce como la representaci on oculta del uno. C omo se podr a almacenar 23.85? Primero es positivo, as el bit de signo es 0, ahora el exponente verdadero es 4, as que el exponente es 7F + 4 = 8316 . Finalmente la fracci on es (recuerde el uno de adelante est a oculto). Colocando todo esto unido (para ayudar a aclarar las diferentes secciones del formato del punto otante, el bit de signo y la fracci on han sido subrayados y los bits se han agrupado en nibles):

Uno deber a tener en cuenta que los bytes 41 BE CC CD se pueden interpretar de diferentes maneras dependiendo qu e hace un programa con ellos. Como el n umero de punto otante de precisi on simple, ellos 0 100 0001 1 011 1110 1100 1100 1100 11002 = 41BECCCC16 representan, pero como un entero, ellos representan. odico). Si uno conLa CPU no conoce cu al es Esto no es exactamente 23.85 (ya que es un binario peri umero anterior a decimal, uno encuentra que es aproximadamente. la interpretaci on correcta. vierte el n

Este n umero es muy cercano a 23.85, pero no es exacto. Actualmente, en C, 23.85 no se puede representar exactamente como arriba. Ya que el bit del

DE PUNTO FLOTANTE 6.1. REPRESENTACION e=0 e=0 e = FF e = FF and f = 0 and f = 0 and f = 0 and f = 0

123

denota el n umero cero (que no puede ser normalizado ) Observe que hay un +0 y -0. denotes un n umero sin normalizar. Estos se discuten en la pr oxima secci on. denota innito (). Hay innitos positivo y negativo. denota un resultado indenido, Conocido como NaN (Not a Number).

Cuadro 6.1: Valores especiales de f y e extremo izquierdo m as signicativo que fue truncado de la representaci on exacta es 1. El u ltimo bit se redondea a 1. As 23.85 podr a ser representado como 41 BE CC CD en hexadecimal usando precisi on simple. Convirtiendo esto a decimal el resultado y que es una aproximaci on ligeramente mejor de 23.85. C omo se podr a representar -23.85? s olo cambie el bit de signo: C1BECCCD no tome el complemento a 2! Ciertas combinaciones de e y f tienen signicado especial para los oat IEEE. La Tabla 6.1 describe estos valores especiales. Un innito se produce por un desborde o por una divisi on por cero. Un resultado indenido se produce por una operaci on no v alida como tratar de encontrar la ra z cuadrada de un n umero negativo, sumar dos innitos, etc. Los n umeros de precisi on simple est an en el rango de 1,0 2126 ( 35 127 1,1755 10 ) to 1,11111 . . . 2 ( 3,4028 1035 ). N umeros sin normalizar Los n umeros sin normalizar se pueden usar para representar n umeros con magnitudes m as peque nas que los normalizados (menores a 1,0 2126 ). Por ejemplo, considere el n umero 1,0012 2129 ( 1,6530 1039 ). En la forma normalizada dada, el exponente es muy peque no. Sin embargo, se puede representar de una forma no normalizada como: 0,010012 2127 . Para almacenar este n umero, el exponente es jado a 0 (ver la tabla 6.1) y la funci on de la mantisa completa del n umero escrito como un producto 127 con 2 (todos los bits son almacenados incluyendo el 1 a la izquierda del punto decimal). La representaci on de 1,001 2129 es entonces: 0 000 0000 0 001 0010 0000 0000 0000 0000

124 63 62 s 52 e 51

CAP ITULO 6. PUNTO FLOTANTE 0 f Figura 6.4: Precisi on doble del IEEE

doble precisi on IEEE La doble precisi on IEEE usa 64 bits para representar n umeros y normalmente son exactos los 15 primeros d gitos decimales signicativos. Como muestra la Figura 6.4, el formato b asico es muy similar a la precisi on simple. Se usan m as bits para el exponente (ll) y la fracci on (52) que para la precisi on 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 precisi on simple). Segundo, se permite un gran rango de exponentes verdaderos (y as usa un gran rango de magnitudes). Las magnitudes de precisi on doble van de 10308 hasta 10308 aproximadamente. En el campo de la fracci on el responsable del incremento en el n umero de d gitos signicativos para los valores dobles. Como un ejemplo, considere nuevamente 23.85 otra vez. El exponente polarizado ser a 4 + 3FF = 403 en hexadecimal. As la representaci on doble ser a: 0 100 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 precisi on doble tiene los mismos valores especiales que la precisi on simple. 3 Los n umeros no normalizados son muy similares tambi en. La principal diferencia es que los n umeros dobles sin normalizados usan 21023 en 127 lugar de 2 .

6.2.

Aritm etica de punto otante

La aritm etica de punto otante en un computador diferente que en la matem atica continua. En la matem aticas, todos los n umeros pueden ser considerados exactos. Como se muestra en la secci on anterior, en un computador muchos n umeros no se pueden representar exactamente con un n umero nito de bits. Todos los c alculos se realizan con una precisi on limitada. En los
3 La u nica diferencia es que para los valores de innito e indenido, el exponente polarizado es 7FF y no FF.

6.2. ARITMETICA DE PUNTO FLOTANTE

125

ejemplos de esta secci on, se usar an n umeros con una mantisa de 8 bits por simplicidad.

6.2.1.

suma

Para sumar dos n umeros de punto otante, los exponentes deben ser iguales. Si ellos, no son iguales, entonces ellos se deben hacer iguales, desplazando la mantisa del n umero con el exponente m as peque no. Por ejemplo, considere 10,375 + 6,34375 = 16,71875 o en binario: 1,0100110 23 + 1,1001011 22 Estos dos n umeros no tienen el mismo exponente as que se desplaza la mantisa para hacer iguales los exponentes y entonces sumar: 1,0100110 23 + 0,1100110 23 10,0001100 23 Observe que el desplazamiento de 1,1001011 22 pierde el uno delantero y luego de redondear el resultado se convierte en 0,1100110 23 . El resultado de la suma, 10,0001100 23 (o 1,00001100 24 ) es igual a 10000,1102 o 16.75. Esto no es igual a la respuesta exacta (16.71875) Es s olo una aproximaci on debido al error del redondeo del proceso de la suma. Es importante tener en cuenta que la aritm etica de punto otante en un computador (o calculadora) es siempre una aproximaci on. Las leyes de las matem aticas no siempre funcionan con n umeros de punto otante en un computador. Las matem aticas asumen una precisi on innita que un computador no puede alcanzar. Por ejemplo, las matem aticas ense nan 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,0000110 24 1,1111111 23 Desplazando 1,1111111 23 da (redondeando arriba) 1,0000000 24 1,0000110 24 1,0000000 24 0,0000110 24

126

CAP ITULO 6. PUNTO FLOTANTE

0,0000110 24 = 0,112 = 0,75 que no es exacto.

6.2.3.

Multiplicaci on y divisi on

Para la multiplicaci on, las mantisas son multiplicadas y los exponentes son sumados. Considere 10,375 2,5 = 25,9375: 1,0100110 23 1,0100000 21 10100110 + 10100110 1,10011111000000 24 Claro est a, el resultado real podr a ser redondeado a 8 bits para dar: 1,1010000 24 = 11010,0002 = 26 La divisi on es m as complicada, pero tiene problemas similares con errores de redondeo.

6.2.4.

Ramicaciones para programar

El principal punto de esta secci on es que los c alculos de punto otante no son exactos. El programador necesita tener cuidado con esto. Un error com un que el programador hace con n umeros de punto otante es compararlos asumiendo que el c alculo es exacto. Por ejemplo considere una funci on llamada f (x) que hace un c alculo complejo y un programa est a tratando de encontrar las ra ces de la funci on. 4 . Uno podr a intentar usar la siguiente instrucci on para mirar si x es una ra z: if ( f (x) == 0.0 ) Pero si f (x) retorna 1 1030 ? Esto probablemente signica que x es una muy buena aproximaci on a una ra z verdadera; sin embargo, la igualdad ser a falsa. Puede no haber ning un valor de punto otante IEEE de x que retorne cero exactamente, debido a los errores de redondeo en f (x). Un m etodo mucho mejor ser a usar: if ( fabs( f (x)) < EPS ) D onde EPS es un macro denido a ser un valor positivo muy peque no (como 1 1010 ). Esto es cierto si f (x) est a muy cercano a cero. En general, comparar un valor de punto otante (digamos x) con otro (y) use: if ( fabs(x y)/fabs(y) < EPS )
4

Una ra z de una funci on es un valor x tal que f (x) = 0

6.3. EL COPROCESADOR NUMERICO

127

6.3.
6.3.1.

El coprocesador num erico


Hardware

Los primeros procesadores Intel no ten an soporte de hardware para las operaciones de punto otante. Esto no signica que ellos no pod an efectuar operaciones de punto otante. Esto s olo signica que ellas se realizaban por procedimientos compuestos de muchas instrucciones que no son de punto otante. Para estos primeros sistemas, Intel suministraba un circuito integrado adicional llamado coprocesador matem atico. Un coprocesador matem atico tiene instrucciones de m aquina que realizan instrucciones de punto otante mucho m as r apido que usando procedimientos de software (en los primeros procesadores se realizaban al menos 10 veces m as r apido). El coprocesador para el 8086/8088 fue llamado 8087. Para el 80286 era el 80287 y para el 80386 un 80387. El procesador 80486DX integr o el coprocesador matem atico en el 80486 en s mismo 5 . Desde el Pentium, todas las generaciones del 80X86 tienen un coprocesador matem atico empotrado; sin embargo el todav a se programa como si fuera una unidad separada. A un los primeros sistemas sin un coprocesador pueden instalar un software que emula el coprocesador matem atico. 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 as lento claro est a). El coprocesador num erico tiene ocho registros de punto otante. Cada registro almacena 80 bits. Los n umeros de punto otante se almacenan en estos registros siempre como n umeros de 80 bits de precisi on extendida. Los registros se llaman STO, ST1, . . . ST7. Los registros de punto otante se usan diferentes que los registros enteros en la CPU. Los registros de punto otante est an organizados como una pila. Recuerde que una pila es una lista LIFO (Last In Firist Out ). STO siempre se reere al valoren el tope de la pila. Todos los n umeros nuevos se a naden al tope de la pila. Los n umeros existentes se empujan en la pila para dejarle espacio al nuevo n umero. Hay tambi en un registro de estado en el coprocesador num erico. Tiene varias banderas. S olo las 4 banderas usadas en comparaciones ser an estudiadas. C0 , C1 , C2 and C3 . El uso de ellas se discutir a luego.

6.3.2.

Instrucciones

Para distinguir f acilmente las instrucciones normales de la CPU de las del coprocesador, todos los nem onicos del coprocesador comienzan por F.
5

Sin embargo el 80486SX no tiene el un coprocesador integrado

128 Carga y almacenamiento

CAP ITULO 6. PUNTO FLOTANTE

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

6.3. EL COPROCESADOR NUMERICO


1 2 3 4 5 6 7 8 9 10 11 12 13

129

segment .bss array resq SIZE sum resq 1 segment .text mov ecx, SIZE mov esi, array fldz lp: fadd qword [esi] add esi, 8 loop lp fstp qword sum

; ST0 = 0 ; ST0 += *(esi) ; se mueve al pr oximo dobule ; alamcena el resultado en sum

Figura 6.5: Ejemplo sumar arreglo

Suma y resta

Cada una de las instrucciones de suma calcula la suma de ST0 y otro operando, el resultado siempre se almacena en un registro del coprocesador. FADD src ST0 += src . src puede ser cualquier registro del coprocesador o un n umero de precisi on simple o doble en memoria. FADD dest, ST0 dest += ST0. El destino puede ser cualquier registro del coprocesador FADDP dest o dest += ST0 entonces se saca de la pila. El destino FADDP dest, ST0 puede ser cualquier registro del coprocesador. FIADD src ST0 += (float) src . suma un entero con ST0. src debe ser una palabra o una palabra doble en memoria. Hay el doble de instrucciones de resta que de suma porque el orden de los operandos es importante. (a + b = b + a, pero a b = b a!). Por cada instrucci on, hay una alterna que resta en el orden inverso. Esas instrucciones al rev es todas culminan con R o RP. La Figura muestra un peque no c odigo que suma los elementos de un arreglo de dobles. En las l neas 10 y 13, uno debe especicar el tama no del operando de memoria. De otra forma el ensamblador no conocer a si el operando de memoria era un oat (dword) o double (qword).

130 FSUB src

CAP ITULO 6. PUNTO FLOTANTE ST0 -= src . src puede ser cualquier registro del coprocesador o un n umero de presici on doble o simple en memoria. ST0 = src - ST0. src puede ser cualquier registro del coprocesador o un n umero de presici on doble o simple en memoria. dest -= ST0. dest puede ser cualquier registro del coprocesador. dest = ST0 - dest . dest puede ser cualquier registro del coprocesador. dest -= ST0 entonces sale de la pila. dest puede ser cualquier registro del coprocesador. dest = ST0 - dest entonces sale de la pila. dest puede ser cualquier registro del coprocesador. ST0 -= (float) src . Resta un entero de ST0. src debe ser una palabra o palabra doble en memoria. ST0 = (float) src - ST0. Resta ST0 de un entero. src debe ser una palabra o palabra doble en memoria.

FSUBR src

FSUB dest, ST0 FSUBR dest, ST0 FSUBP dest o FSUBP dest, STO FSUBRP dest o FSUBRP dest, STO FISUB src FISUBR src

Multiplicaci on y divisi on

La instrucci on de multiplicaci on son totalmente an alogas a las instrucciones de suma. FMUL src ST0 *= src . src puede ser cualquier registro del coprocesador o un n umero de precisi on simple o doble en memoria. dest *= ST0. dest puede ser cualquier registro del coprocesador. dest *= ST0 entonces sale de la pila. dest puede ser cualquier registro del coprocesador. ST0 *= (float) src . Multiplica un entero con ST0. src debe ser una palabra o palabra doble en memoria.

FMUL dest, ST0 FMULP dest o FMULP dest, STO FIMUL src

No es sorprendente que las instrucciones de divisi on son an alogas a las instrucciones de resta. La divisi on por cero de un innito.

6.3. EL COPROCESADOR NUMERICO FDIV src

131

FDIVR src

FDIV dest, ST0 FDIVR dest, ST0 FDIVP dest o FDIVP dest, STO FDIVRP dest o FDIVRP dest, STO FIDIV src FIDIVR src

ST0 /= src . src puede ser cualquier registro del coprocesador o un n umero de precisi on simple o doble en memoria. ST0 = src / ST0. src puede ser cualquier registro del coprocesador o una n umero de precisi on simple o doble en memoria. dest /= ST0. dest puede ser cualquier registro del coprocesador. dest = ST0 / dest . dest puede ser cualquier registro del coprocesador. dest /= ST0 entonces sale de la pila. dest puede ser cualquier registro del coprocesador. dest = ST0 / dest entonces sale de la pila. dest puede ser cualquier registro del coprocesador. ST0 /= (float) src . Divide ST0 por un entero. src debe ser una palabra o palabra doble en memoria. ST0 = (float) src / ST0. Divide un entero por ST0. src debe ser una palabra o palabra doble en memoria.

Comparaciones El coprocesador tambi en realiza comparaciones de n umeros de punto otante. La f ormula de instrucciones FCOM hacen esta operaci on. compara ST0 y src . src puede ser un registro del coprocesador o un oat o double en memoria. puede ser un FCOMP src compara ST0 y src , luego sale de la pila. src puede ser un registro del coprocesador o un oat o double en memoria. FCOMPP compara ST0 y ST1, entonces saca de la pila dos veces. FICOM src compara ST0 y (float) src . src puede ser una palabra o palabra dobel en memoria. FICOMP src compara ST0 y (float)src , entonces saca de la pila. src puede ser una palabra o palabra doble entera en memoria. FTST compara ST0 and 0. Estas instrucciones cambian los bits C0 , C1 , C2 y C3 del registro de estado del coprocesador. Desafortunadamente no es posible que la CPU acceda a estos bits directamente. Las instrucciones de salto condicional usan el registro FLAGS, no el registro de estado del coprocesador. Sin embargo es relativamente f acil transferir los bits de la palabra de estado a los bits correspondientes del registro FLGS usando algunas instrucciones nuevas. FCOM src

132
1 2 3 4 5 6 7 8 9 10 11 12 13 14

CAP ITULO 6. PUNTO FLOTANTE

; ;

if ( x > y ) fld fcomp fstsw sahf jna qword [x] qword [y] ax else_part ; ST0 = x ; compara STO and y ; mueve los bits C a FLAGS ; si x no es mayor que y, ; vaya a else_part

then_part: ; c odigo para parte de entonces jmp end_if else_part: ; c odigo para parte si no end_if:

Figura 6.6: Ejemplo de comparaci on Almacena la palabra de estado del coprocesador in memoria o en el registro AX. SAHF Almacena el registro AH en el registro FLAGS. LAHF Carga el registro AH con los bits del registro FLAGS. La Figura 6.6 muestra un ejemplo cortico. Las l neas 5 y 6 transeren los bits C0 , C1 , C2 y C3 de la palabra de estado del coprocesador al registro FLAGS. Los bits son transferidos tal que ellos son an alogos al resultado de una comparaci on de dos enteros sin signo. Este es el porque la l nea 7 usa la instrucci on JNA. El Pentium Pro (y procesadores posteriores II y III) soportan 2 nuevos operadores de comparaci on que directamente modican el registro FLAGS de la CPU. FCOMI src compara ST0 y src . src debe ser un registro del coprocesador. FCOMIP src compara ST0 y src , entonces se saca de la pila. src debse ser un registro del coprocesador. La Figura 6.7 muestra una subrutina de ejemplo que encuentran el m aximo de dos n umeros tipo double usando la instrucci on FCOMIP. No confundan esta instrucci on con la funci on de comparaci on de enteros (FICOMP y FICOM). FSTSW dest

Instrucciones miscelaneas Esta secci on cubre otras instrucciones que suministra el procesador:

6.3. EL COPROCESADOR NUMERICO FCHS FABS FSQRT FSCALE

133

ST0 = - ST0 cambia el bit de signo de ST0 ST0 = | ST0| Toma el valor absoluto de ST0 ST0 = STO Toma la ra z cuadrada de ST0 ST0 = ST0 2 ST1 multiplica ST0 por una potencia de dos r apidamente. ST1 no se quita de la pila del coprocesador . La Figure 6.8 muestra un ejemplo de c omo usar esta instrucci on.

6.3.3. 6.3.4.

Ejemplos F ormula cuadr atica

El primer ejemplo muestra c omo se puede programar la f ormula cuadr atica en ensamblador. Recuerde que la f ormula cuadr atica calcula la soluci on de una ecuaci on cuadr aticai: ax2 + bx + c = 0 La f ormula en s misma da dos soluciones para x: x1 y x2 . b b2 4ac x1 , x2 = 2a La expresi on dentro de la ra z cuadrada (b2 4ac) es llamada discriminante, su valor es u til para determinar las 3 posibilidades para la soluci on: 1. Hay solo un real en la soluci on degenerad. b2 4ac = 0 2. Hay dos soluciones reale. b2 4ac > 0 3. Hay dos soluciones complejas. b2 4ac < 0 Este es un peque no programa en C que usa la subrutina en ensamblador. quadt.c #include <stdio.h> int quadratic ( double, double, double, double , double ); int main() { double a,b,c , root1 , root2; printf (Enter a , b , c : ); scanf( %lf %lf % lf , &a, &b, &c); if ( quadratic ( a , b , c, &root1, &root2 ) )

134

CAP ITULO 6. PUNTO FLOTANTE

printf (roots : %.10g %.10g\n, root1 , root2 ); else printf (No real roots \n); return 0; } quadt.c A continuaci on est a la rutina en ensamblador.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32

; ; ; ; ; ; ; ; ; ; ; ;

quad.asm funci on quadratic Halla la soluci on de la ecuaci on cuadr atica: a*x^2 + b*x + c = 0 Prototipo de C: int quadratic( double a, double b, double c, double * root1, double *root2 ) Par ametros: a, b, c - Coeficientes de la ecuaci on cuadr atica (ver arriba) root1 - Apuntador al double que almacena la primera ra z root2 - Apuntador al double que almacena la segunda ra z Valor de retorno: devuelve 1 si las ra ces son reales si no 0 a b c root1 root2 disc one_over_2a qword qword qword dword dword qword qword [ebp+8] [ebp+16] [ebp+24] [ebp+32] [ebp+36] [ebp-8] [ebp-16]

%define %define %define %define %define %define %define

segment .data MinusFour segment .text global _quadratic: push mov sub push

dw

-4

_quadratic ebp ebp, esp esp, 16 ebx

; asigna 2 doubles (disc & one_over_2a) ; debe guardar el valor original de ebx

6.3. EL COPROCESADOR NUMERICO


33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74

135

fild fld fld fmulp fmulp fld fld fmulp faddp ftst fstsw sahf jb fsqrt fstp fld1 fld fscale fdivp fst fld fld fsubrp fmulp mov fstp fld fld fchs fsubrp fmul mov fstp mov jmp

word [MinusFour]; pila -4 a ; pila: a, -4 c ; pila: c, a, -4 st1 ; pila: a*c, -4 st1 ; pila: -4*a*c b b ; pila: b, b, -4*a*c st1 ; pila: b*b, -4*a*c st1 ; pila: b*b - 4*a*c ; prueba con 0 ax no_real_solutions ; disc ; ; a ; ; st1 ; one_over_2a ; b ; disc ; st1 ; st1 ; ebx, root1 qword [ebx] ; b ; disc ; ; st1 ; one_over_2a ; ebx, root2 qword [ebx] ; eax, 1 ; short quit ; if disc < 0, no hay soluciones reales pila: sqrt(b*b - 4*a*c) almacena y saca de la pila pila: 1.0 pila: a, 1.0 pila: a * 2^(1.0) = 2*a, 1 pila: 1/(2*a) pila: 1/(2*a) pila: b, 1/(2*a) pila: disc, b, 1/(2*a) pila: disc - b, 1/(2*a) pila: (-b + disc)/(2*a) almacena en *root1 pila: b pila: disc, b pila: -disc, b pila: -disc - b pila: (-b - disc)/(2*a) almacena en *root2 valor de retorno es 1

no_real_solutions: mov eax, 0 quit: pop mov ebx esp, ebp

; valor de retorno es 0

136
75 76

CAP ITULO 6. PUNTO FLOTANTE pop ret ebp quad.asm

6.3.5.

Leer arreglos de archivos

Este ejemplo, es una rutina en ensamblador que lee doubles de un archivo. Sigue un peque no programa de prueba en C. readt.c / Este programa prueba el procedimiento en ensamblador bit read doubles () . Lee doubles de stdin . ( use la o redireccion para leer desde un archivo .) / #include <stdio.h> extern int read doubles ( FILE , double , int ); #dene MAX 100 int main() { int i ,n; double a[MAX]; n = read doubles( stdin , a , MAX); for ( i =0; i < n; i ++ ) printf ( %3d %g\n, i, a[i ]); return 0; } readt.c A continuaci on la rutina en ensamblador
1 2 3 4 5 6 7 8

segment .data format db segment .text global extern

read.asm "%lf", 0 ; format for fscanf()

_read_doubles _fscanf 8

%define SIZEOF_DOUBLE

6.3. EL COPROCESADOR NUMERICO


9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50

137

%define %define %define %define ; ; ; ; ; ; ; ; ; ; ; ;

FP ARRAYP ARRAY_SIZE TEMP_DOUBLE

dword [ebp + 8] dword [ebp + 12] dword [ebp + 16] [ebp - 8]

funci on _read_doubles prototipo de C: int read_doubles( FILE * fp, double * arrayp, int array_size ); Esta funci on lee dobles de un archivo de texto hasta EOF o hasta que se llene el arreglo Par ametros: fp - apuntador FILE pointer a leer desde (se debe abrir para entrada) arrayp - apuntador a un arreglo de doubles para leer en el array_size - n umero de elementos en el arreglo Valor de retorno: n umero de doubles almacenado en el arreglo (en EAX)

_read_doubles: push mov sub push mov xor

ebp ebp,esp esp, SIZEOF_DOUBLE esi esi, ARRAYP edx, edx

; define un double en la pila ; guarda esi ; esi = ARRAYP ; edx = ndice del arreglo (inicialmente en 0)

while_loop: cmp edx, ARRAY_SIZE ; si edx < ARRAY_SIZE? jnl short quit ; si no, salir del bucle ; ; llama a fscanf() para leer un double en TEMP_DOUBLE ; fscanf() podr a cambiar edx y as guardarlo ; push edx ; guarda edx lea eax, TEMP_DOUBLE push eax ; push &TEMP_DOUBLE push dword format ; push &format push FP ; push el apuntador al archivo call _fscanf add esp, 12 pop edx ; restaura edx

138
51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73

CAP ITULO 6. PUNTO FLOTANTE cmp jne eax, 1 short quit ; fscanf retorn o 1? ; si no, salir del bucle

; ; copia TEMP_DOUBLE en ARRAYP[edx] ; (Los ocho bytes del dobule son copiados por las dos copias de 4 bytes) ; mov eax, [ebp - 8] mov [esi + 8*edx], eax ; primero copia los 4 bytes inferiores mov eax, [ebp - 4] mov [esi + 8*edx + 4], eax ; ahora copia los 4 bytes superiores inc jmp quit: pop mov mov pop ret esi eax, edx esp, ebp ebp read.asm ; restaura esi ; almacena el valor de retorno en eax edx while_loop

6.3.6.

Hallar primos

Este u ltimo ejemplo halla n umeros primos una vez m as. Esta implementaci on es m as eciente que la anterior. Almacena los primos que ha encontrado en un arreglo y solo divide por los primos que ha encontrado antes, en lugar de cada n umero impar, para encontrar nuevos primos. Otra diferencia es que calcula la ra z cuadrada del n umero para buscar el pr oximo primo a determinar en qu e punto parar la b usqueda de factores. Altera la palabra de control de coprocesador tal que cuando almacena la ra z cuadrada como un entero, y la trunca en lugar de redondearla. Esto es controlado por los bits 10 y 11 de la palabra de control. Estos bits se llaman los bits RC (Raunding Control). Si ellos son ambos 0 (el defecto) el coprocesador redondea cuando convierte a entero. Si ellos son ambos 1, el coprocesador trunca las conversiones enteras. Observe que la rutina se cuida de guardar la palabra de control original y restaurarla antes de retornar. A continuaci on el programa en C: fprime.c

6.3. EL COPROCESADOR NUMERICO #include <stdio.h> #include <stdlib.h> / o funcin nd primes Halla los u nmeros primos indicados aParmetros: a arreglo que almacena los primos n acuntos primos encontrar / extern void nd primes ( int a , unsigned n ); int main() { int status ; unsigned i; unsigned max; int a; printf ( aCuntos primos desea encontrar Ud.? ); scanf( %u, &max); a = calloc ( sizeof ( int ), max); if ( a ) { nd primes (a,max); / imprime los u 20 ltimos primos encontrados / for ( i = ( max > 20 ) ? max 20 : 0; i < max; i++ ) printf ( %3d %d\n, i+1, a[i]); free (a ); status = 0;

139

} else { fprintf ( stderr , No se puede crera el arreglo de %u ints \n, max); status = 1; } return status ; }

140

CAP ITULO 6. PUNTO FLOTANTE fprime.c A continuaci on la rutina en ensamblador:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38

prime2.asm segment .text global _find_primes ; ; function find_primes ; Encuentra el n umero indicado de primos ; Par ametros: ; array - arreglo que almacena los primos ; n_find - cu antos primos encontrar ; Prototipo de C ;extern void find_primes( int * array, unsigned n_find ) ; %define array ebp + 8 %define n_find ebp + 12 %define n ebp - 4 ; n umero de primos a encontrar %define isqrt ebp - 8 ; el piso de la ra z cudrada de guess %define orig_cntl_wd ebp - 10 ; palabra original de control %define new_cntl_wd ebp - 12 ; nueva palabra de control _find_primes: enter push push fstcw mov or mov fldcw mov mov mov mov mov

12,0 ebx esi word [orig_cntl_wd] ax, [orig_cntl_wd] ax, 0C00h [new_cntl_wd], ax word [new_cntl_wd] esi, [array] dword [esi], 2 dword [esi + 4], 3 ebx, 5 dword [n], 2

; Hace espacio para las variables locales ; guarda las posibles variables registro

; toma la palabra de control actual ; fija el redondeo de los bits a 11 (truncar)

; ; ; ; ;

esi apunta a array array[0] = 2 array[1] = 3 ebx = guess = 5 n = 2

; ; Este bucle externo encuentra un nuevo primo en cada iteraci on, ; que se a~ nade al final del arreglo. A diferencia del primer programa de

6.3. EL COPROCESADOR NUMERICO


39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80

141

; ; ; ;

primos, esta funci on no determina los primos dividiendo por todos los n umeros impares. S olo divide por los n umeros primos que ya se han encontrado. (Esta es la raz on por la cual ellos se han almacenado en el arreglo)

while_limit: mov cmp jnb mov push fild fsqrt fistp

eax, [n] eax, [n_find] short quit_limit ecx, 1 ebx dword [esp] pop ebx dword [isqrt]

; while ( n < n_find )

; ecx se usa como ndice del arreglo ; almacena guess en la pila ; carga guess en la pila del coprocesador ; saca guess de la pila ; halla sqrt(guess) ; isqrt = floor(sqrt(quess))

; Este b1ucle interno divide guess por los n umeros primos ya encontrados ; hasta que encuentre un factor primo de guess (que significa que guess ; no es primo) o hasta que n umero primo a dividir sea m as grande que ; floor(sqrt(guess)) ; while_factor: mov eax, dword [esi + 4*ecx] ; eax = array[ecx] cmp eax, [isqrt] ; while ( isqrt < array[ecx] jnbe short quit_factor_prime mov eax, ebx xor edx, edx div dword [esi + 4*ecx] or edx, edx ; && guess % array[ecx] != 0 ) jz short quit_factor_not_prime inc ecx ; intenta el pr oximo primo jmp short while_factor ; ; found a new prime ! ; quit_factor_prime: mov eax, [n] mov dword [esi + 4*eax], ebx inc eax mov [n], eax

; suma al final de arreglo ; inc n

142
81 82 83 84 85 86 87 88 89 90 91 92 93

CAP ITULO 6. PUNTO FLOTANTE

quit_factor_not_prime: add ebx, 2 jmp short while_limit quit_limit: fldcw pop pop leave ret word [orig_cntl_wd] esi ebx

; intenta con el impar siguiente

; restaura la palabra de control ; restaura las variables de registro

prime2.asm

6.3. EL COPROCESADOR NUMERICO

143

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28

global _dmax segment .text ; function _dmax ; retorna el mayor de dos argumentos double ; Prototipo de C ; double dmax( double d1, double d2 ) ; Par ametros: ; d1 - primer double ; d2 - segundo double ; Valor de retorno: ; El mayor de d1 y d2 (en ST0) %define d1 ebp+8 %define d2 ebp+16 _dmax: enter 0, 0 fld fld fcomip jna fcomp fld jmp d2_bigger: exit: leave ret qword qword st1 short st0 qword short [d2] [d1] d2_bigger [d1] exit ; pop d2 de la pila ; ST0 = d1 ; si d2 is max, no hace nada

; ST0 = d1, ST1 = d2 ; ST0 = d2

Figura 6.7: Ejemplo de FCOMIP

144

CAP ITULO 6. PUNTO FLOTANTE

1 2 3 4 5 6 7 8

segment .data x dq five dw

2.75 5

; convertido a formato double

segment .text fild dword [five] fld qword [x] fscale

; ST0 = 5 ; ST0 = 2.75, ST1 = 5 ; ST0 = 2.75 * 32, ST1 = 5

Figura 6.8: Ejemplo de FSCALE

Cap tulo 7

Estructuras y C++
7.1.
7.1.1.

Estructuras
Introducci on

Las estructuras se usan en C para agrupar datos relacionados en una variable compuesta. Esta t ecnica tiene varias ventajas: 1. Clarica el c odigo mostrando que los datos denidos en la estructura est an relacionados ntimamente. 2. Simplica el paso de datos a las funciones. En lugar de pasar muchas variables separadas, ellas se pueden pasar como uno una variable. 3. Incrementar la localidad del c odigo.
1

Desde el punto de vista del ensamblador una estructura se puede considerar como un arreglo con elementos de diferente tama no. Los elementos de los arreglos reales son siempre del mismo tipo y mismo tama no. Esta caracter stica es la que le permite a uno calcular la direcci on de cualquier elemento conociendo la direcci on de inicio del arreglo, el tama no de los elementos y el ndice del elemento que se desea. A structures elements do not have to be the same size (and usually are not). Because of this each element of a structure must be explicitly specied and is given a tag (or name) instead of a numerical index. En ensamblador los elementos de una estructura se acceder an de una manera parecida que los elementos de un arreglo. Para acceder a un elemento uno debe conocer la direcci on de inicio de la estructura y el desplazamiento relativo desde el comienzo de la estructura, sin embargo, a diferencia de
1 vea la secci on de manejo de memoria virtual de cualquier libro de texto de sistemas operativos para la discusi on del t ermino.

145

146

CAP ITULO 7. ESTRUCTURAS Y C++ Oset 0 2 6 z Figura 7.1: Estructura S Element x y

los arreglos donde este desplazamiento se puede calcular por el ndice del elemento de una estructura el compilador le asigna un desplazamiento. Por ejemplo considere la siguiente estructura: struct S { short int x ; int y; double z; }; / entero de 2 bytes / / entero de 4 bytes / / oat de 8 bytes /

La Figura 7.1 muestra c omo una variable de tipo S podr a verse en la memoria del computador. La norma ANSI dicta que los elementos de una estructura son colocados en memoria en el mismo orden que ellos son denidos en la estructura. Tambi en dicta que el primer elemento est a al comienzo de la estructura (desplazamiento cero). Tambi en dene otro macro u til en el archivo de cabecera stddef.h llamado offsetof(). Este macro calcula y retorna el desplazamiento de cualquier elemento de una estructura. Este macro toma dos par ametros, el primero es el nombre de tipo de la estructura, el segundo del nombre del elemento al cual encontrarle el desplazamiento. As el resultado de offsetof(S,y) podr a ser 2 seg un la Figura 7.1.

7.1.2.

Alineamiento de la memoria

Si uno usa el macro offsetof para encontrar el desplazamiento de y usando el compilador gcc uno encontrar a que retorna 4 y no 2. Por qu e? Recuerde que una direcci on Porque gcc (y muchos otros compiladores) alinean por defecto las variables est a en los l mites de una a los l mites de palabras dobles. En el modo protegido de 32 bits, la CPU palabra doble si es divisible lee la memoria m as r apido si el dato condensa en el l mite de una palabra por cuatro. doble. La Figura 7.2 muestra c omo se ve realmente la estructura S usando gcc. El compilador inserta dos bytes no usados en la estructura para alinear y(y z) en un l mite de palabra doble. Esto muestra por qu e es buena idea usar offsetof para calcular los desplazamientos en lugar de calcularlos uno mismo. Cuando se usan estructuras denidas en C.

7.1. ESTRUCTURAS Oset 0 2 4 8 z Figura 7.2: Estructura S Element x unused y

147

Claro est a, sin la estructura solo se usa en ensamblador el programador puede determinar el desplazamiento el mismo. Sin embargo si uno est a interfazando C y ensamblador muy importante que los dos c odigos est en de acuerdo en los desplazamientos de los elementos de la estructura. Una complicaci on es que diferentes compiladores de C tienen desplazamientos diferentes de los elementos. Por ejemplo, como se ha visto, el compilador gcc crea una estructura S que se ve como la Figura 7.2; sin embargo, el compilador de Borland podr a crear una estructura como la de la Figura 7.1. Los compiladores de C suministran maneras de especicar el alineamiento usado para los datos. Sin embargo la norma ANSI C no espec ca c omo se hace esto y as , cada compilador lo har a a su manera. El compilador gcc tiene un m etodo exible y complicado de especicar el Alineamiento. El compilador le permite a uno especicar el alineamiento de cualquier tipo usando una sintaxis especial. Por ejemplo, la siguiente l nea: typedef short int unaligned int attribute (( aligned (1)));

Dene un nuevo tipo llamado unaligned int que se alinea en los l mites de los bytes (s , el par entesis despu es de attribute se necesitan). El 1 en el par ametro de aligned se puede reemplazar con otras potencias de dos para especicar otros alineamientos (2 para alineamiento de palabra, 4 para el alineamiento de palabras dobles). Si el elemento y de la estructura a colocar y en el fue cambiado para ser un tipo unaligned int, gcc podr desplazamiento 2. Sin embargo z podr a estar a una distancia de 8 ya que las palabras dobles tienen un alineamiento de palabra por defecto. La denici on del tipo de z tendr a que ser cambiada para colocarlos una distancia de 6. El compilador gcc tambi en permite embalar una estructura. Esto le dice al compilador que use el m nimo de espacio para la estructura. La Figura 7.3 muestra c omo se podr a escribir S de esta manera. De esta forma S usar a el menor n umero de bytes posibles, 14. Los compiladores de Borland y Microsoft, soportan el mismo m etodo de especicar el alineamiento usando la directiva #pragma.

148

CAP ITULO 7. ESTRUCTURAS Y C++

struct S { short int x ; / entero de 2 bytes / int y; / entero de 4 bytes / double z; / oat de 8 bytes / } attribute ((packed)); 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 x ; int y; double z; };

/ entero de 2 bytes / / entero de 4 bytes / / oat de 8 bytes /

#pragma pack(pop)

/ restaura el alineamiento original /

Figura 7.4: Estructura embalada usando 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). El uno se puede reemplazar con dos, cuatro, ocho o diez y seis para especicar el alineamiento en una palabra, palabra doble, palabra cu adruple y p arrafo respectivamente. Esta directiva tiene efecto hasta que se sobreescriba por otra directiva. Esto puede causar problemas ya que estas directivas se usan a menudo en los archivos de encabezamiento. Si el archivo de encabezamiento se incluye antes que otros archivos de encabezamiento con estructuras, esas estructuras pueden estar diferente que las que tendr an por defecto. Esto puede conducir un error muy dif cil de encontrar. Los m odulos de un programa podr an desordenar los elementos de las estructuras de otros m odulos. Hay una manera de evitar este problema. Microsoft y Borland tienen una manera de guardar el alineamiento y restaurarlo luego. La Figura 7.4 muestra c omo se har a esto.

7.1.3.

Campos de bits

Los campos de bit le permiten a uno especicar miembros de una estructura que usa un n umero especicado de bits. El tama no de los bits no

7.1. ESTRUCTURAS struct S { unsigned unsigned unsigned unsigned };

149

f1 f2 f3 f4

: : : :

3; 10; 11; 8;

/ / / /

campo campo campo campo

de de de de

3 bits 10 bits 11 bits 8 bits

/ / / /

Figura 7.5: Ejemplo campo de bits tiene que ser m ultiplo de 8. Un campo de bits miembro est a denido como un unsigned int o un int con dos puntos y luego el tama no en bits. La Figura 7.5 muestra un ejemplo. Esto dene una variable de 32 bits que se descompone en las siguientes partes: 8 bits f4 11 bits f3 10 bits f2 3 bits f1

El primer campo est a asignado a los bits menos signicativos de esta palabra doble. 2 Sin embargo, el formato no es tan simple, si uno mira c omo se almacenan los bits en memoria. La dicultad ocurre cuando los campos de bits se salen de los l mites del byte. Porque los bytes en un procesador little endian se invertir an en memoria. Por ejemplo, los campos de la estructura S se ver an en memoria. 5 bits 3 bits 3 bits 5 bits 8 bits 8 bits f2l f1 f3l f2m f3m f4 La etiqueta f2l se reere a los u ltimos cinco bits (los 5 bits menos signicativos) del campo f2. La etiqueta f2m se reere a los 5 bits m as signicativos de f2. Las l neas verticales dobles muestran los l mites de los bytes. Si uno invierte todos los bytes, las partes de los campos f2 y f3 se colocar an en el lugar correcto. La forma de la memoria f sica no es importante a menos que los datos se transeran a o fuera de nuestro programa (lo cual es muy poco com un con campos de bits). es com un para dispositivos de hardware usar n umeros impares de bits donde los campos de bits son u tiles para representarlos. Un ejemplo es SCSI: 3 Una orden de lectura directa para un dispositivo SCSI se especica enviando un mensaje de 6 bits al dispositivo con el formato especicado en la Figura 7.6. La dicultad de representar esto usando
2 Actualmente, la norma ANSI/ISO C le da al compilador alguna exibilidad de como se ordenan los bits exactamente. Sin embargo los compiladores populares (gcc, Microsoft y Borland ) usar an este orden en los campos. 3 Small Computer System Interface, es una norma de la industria para discos duros y etc.

150 Byte \ Bit 0 1 2 3 4 5 7 6

CAP ITULO 7. ESTRUCTURAS Y C++ 5 4 3 2 1 0 Codigo de operaci on (08h) Unidad L ogica # msb de LBA mitad de la direcci on del bloque l ogico lsb de direcci on del bloque l ogico Longitud de transferencia Control

Figura 7.6: Formato de la orden leer de SCSI campos de bits es la direcci on del bloque l ogico que abarca 3 bytes diferentes en la orden. De la Figura 7.6 uno ve que el dato se almacena en el formato big endian. La Figura 7.7 muestra una denici on que intenta trabajar con todos los compiladores. Las dos primeras l neas denen un macro que retorna verdadero si el c odigo se compila con los compiladores de Borland o Microsoft. La parte potencialmente confusa est a en las l neas 11 a 14. Primero uno podr a sorprenderse de Por qu e los bits lba mid y lba lsb est an denidos separados y no en un solo campo de 16 bits? La raz on es que el dato est a en big endian. Un campo de 16 bits podr a ser almacenado con el orden little endian por el compilador. Ahora, los campos lba msb y logical unit parecen invertidos; sin embargo esto no es el caso. Ellos tienen que colocarse en este orden. La Figura 7.8 muestra c omo se colocan los campos en una entidad de 48 bits (los l mites del byte est an una vez m as marcados con l neas dobles). Cuando esto se almacena en memoria en el orden little endian, los bits se colocan en el orden deseado.(Figura 7.6) Para complicar las cosas m as, la denici on para SCSI read cmd no trabaja correctamente en el compilador de Microsoft. Si se eval ua la expresi on sizeof (SCSI read cmpl), Microsoft C retornar a 8 no 6. Esto es porque el compilador de Microsoft usa el tipo de campo de datos para determinar c omo colocar los bits. Ya que los bits est an denidos como de tipo unsigned, el compilador rellena dos bytes al nal de la estructura para tener un m ultiplo de palabras dobles. Esto se puede solucionar haciendo todos los campos unsigned short. Ahora el compilador de Microsoft no necesita agregar ning un relleno ya que los 6 bytes son un m ultiplo de una palabra 4 Los otros compiladores tambi en trabajar an correctamente con este cambio. La Figura 7.9 muestra otra denici on que trabaja con los tres compiladores. Se evitan todos los problemas usando campos de tipo unsigned char. El lector no deber a entristecerse si encuentra la divisi on anterior confusa. Es confusa! El autor a menudo encuentra menos confuso evitar los
4 Mezclar diferentes tipos de campos de bits produce un comportamiento muy confuso. Se invita al lector a experimentar al respecto

7.1. ESTRUCTURAS #dene MS OR BORLAND (dened( BORLANDC ) \ || dened ( MSC VER)) #if MS OR BORLAND # pragma pack(push) # pragma pack(1) #endif struct SCSI read cmd { unsigned opcode : 8; unsigned lba msb : 5; unsigned logical unit : 3; unsigned lba mid : 8; / bits de la mitad / unsigned lba lsb : 8; unsigned transfer length : 8; unsigned control : 8; } #if dened( GNUC ) attribute ((packed)) #endif ; #if MS OR BORLAND # pragma pack(pop) #endif Figura 7.7: Estructura del formato de la orden leer de SCSI

151

campos de bits y en su lugar usar las operaciones entre bits para examinar y modicar los bits manualmente.

7.1.4.

Usando estructuras en ensamblador

Como se discuti o antes, acceder a una estructura en ensamblador es muy parecido a acceder a un arreglo. Por ejemplo considere c omo uno podr a escribir una rutina en ensamblador que iniciar a con cero el elemento y de una estructura S. Asumiendo que el prototipo de la rutina ser a: void zero y ( S s p ); La rutina en ensamblador ser a:

152 8 bits control 8 bits transfer length

CAP ITULO 7. ESTRUCTURAS Y C++ 8 bits lba lsb 8 bits lba mid 3 bits logical unit 5 bits lba msb 8 bits opcode

Figura 7.8: Ordenamiento de los campos de SCSI read cmd struct SCSI read cmd { unsigned char opcode; unsigned char lba msb : 5; unsigned char logical unit : 3; unsigned char lba mid; / bits de la mitad/ unsigned char lba lsb ; unsigned char transfer length ; unsigned char control; } #if dened( GNUC ) attribute ((packed)) #endif ; Figura 7.9: Estructura alterna del formato de la orden leer de SCSI %define _zero_y: enter mov mov leave ret y_offset 4

1 2 3 4 5 6 7 8

0,0 eax, [ebp + 8]

; toma s_p (apuntador a la estructura) ; de la pila dword [eax + y_offset], 0

C le permite a uno pasar estructuras por valor a una funci on; sin embargo esto la mayor a de las veces es mala idea. Cuando se pasa por valor, todos los datos de la estructura se deben copiar en la pila y luego tra dos por la rutina. Es mucho m as eciente pasar un apuntador a una estructura. C Tambi en permite que un tipo de estructura se use como un valor de retorno. Obviamente la estructura no se puede retornar en el registro EAX. Cada compilador maneja la situaci on diferente. Una soluci on com un es que el compilador usa esto para internamente reescribir la funci on para que tome una estructura como par ametro. El apuntador se usa para colocar el valor de retorno en una estructura denida fuera de la rutina llamada. La mayor a de los ensambladores (incluido NASM) tiene un soporte empotrado para denir estructuras en su c odigo de ensamblador. Consulte su

7.2. ENSAMBLADOR Y C++ #include <stdio.h> void f ( int x ) { printf ( %d\n, x); } void f ( double x ) { printf ( %g\n, x); } Figura 7.10: Dos funciones f() documentaci on para los detalles.

153

7.2.

Ensamblador y C++

El lenguaje de programaci on C++ es una extensi on del lenguaje C. Muchas de las reglas b asicas para interfazar C y ensamblador tambi en son v alidas para C++. Sin embargo algunas reglas necesitan ser modicadas. Tambi en algunas de las extensiones de C++ son f aciles de entender con conocimiento del lenguaje ensamblador. Esta secci on asume un conocimiento b asico de C++.

7.2.1.

Manipulaci on de la sobrecarga de nombres

C++ permite que se denan diferentes funciones (y funciones miembro de clases) con el mismo nombre. Cuando m as de una funci on comparte el mismo nombre, se dice que la funci on est a sobrecargada. Si se denen dos funciones con el mismo nombre en C, el encadenador producir a un error porque el encontrar a dos deniciones para el mismo s mbolo en los archivos objeto que est a encadenando. Por ejemplo considere el c odigo de la Figura 7.10. El c odigo en ensamblador equivalente denir a 2 etiquetas f que obviamente ser a un error. C++ usa el mismo proceso de encademanmiento que C, pero evita este error haciendo una manipulaci on de nombres o modicando el s mbolo usado para etiquetar la funci on. De alguna manera C usa la manipulaci on de nombres tambi en. A nade un gui on bajo a la etiqueta de la funci on de C cuando se crea la etiqueta para la funci on. Sin embargo C manipular a el nombre de ambas funciones en la Figura 7.10 de la misma manera y produce

154

CAP ITULO 7. ESTRUCTURAS Y C++

un error. C++ usa un proceso de manipulaci on m as sosticado que produce dos etiquetas diferentes para las funciones. Por ejemplo, a la primera funci on de la Figura 7.10 le podr a asignar DJGPP la etiqueta f Fi y a la segunda funci on, f Fd. Esto evita errores de encadenamiento. Desafortunadamente no est a normalizado c omo se manejan los nombres en C++ y cada compilador trata los nombres a su manera. Por ejemplo Borland C++ podr a usar las etiquetas @f$qi y @f$qd para las dos funciones de la Figura 7.10. Sin embargo, las reglas no son completamente arbitrarias. El nombre manipulado codica la rma de la funci on. La rma de una funci on est a denida por el orden y el tipo de sus par ametros. Observe que la funci on que toma un solo par ametro int tiene una i al nal de la etiqueta manipulada (para DJGPP y Borland) y la que toma un argumento double tiene una d al nal de la etiqueta manipulada. Si tuviera una funci on llamada f con el prototipo. void f ( int x , int y , double z); DJGPP podr a manipular el nombre para ser f Fiid y Borland a @f$qiid. El tipo de retorno de una funci on no hace parte de la rma de la funci on y no se codica en el nombre manipulado. Este hecho explica una regla de sobre carga en C++. S olo funciones cuya rma es u nica se pueden sobrecargar. Como uno puede ver, si dos funciones, con el mismo nombre y rma, est an denidas en C++ ellas producir an el mismo nombre manipulado y crear an un error de encadenamiento. Por defecto C++ manipula los nombres de las funciones as no est en sobrecargadas. Cuando se compila un archivo el compilador no tiene manera de saber si una funci on en particular est a sobrecargada o no, as que manipula todos los nombres. De hecho tambi en manipula los nombres de las variables globales codicando el tipo de variable de manera similar a la rma de las funciones. As uno puede denir una variable global en un archivo como de cierto tipo y entonces intentar usarla en otro archivo como de un tipo err oneo, se producir a un error del encadenador. Esta caracter stica de C++ es conocida como encadenamiento seguro de tipos . Tambi en se expone a otro tipo de error, prototipos inconsistentes. Esto ocurre cuando la denici on de una funci on en un m odulo no concuerda con el prototipo usado por otro m odulo. En C esto puede ser un problema muy dif cil de depurar. C no atrapa este error. El programa compilar a y encadenar a, pero tendr a un comportamiento indenido cuando se invoca el c odigo colocando diferentes tipos de datos en la pila de los que la funci on espera. En C++, esto producir a un error de encadenado. Cuando el compilador de C++ est a examinando la sintaxis un llamado a funci on, el busca una funci on donde emparejen los tipos de los argumentos pasados a la funci on. 5 Si se empareja entonces crea un CALL a la funci on
5

El emparejamiento no tiene que ser exacto, el compilador considerar a las conversiones

7.2. ENSAMBLADOR Y C++

155

correcta usando las reglas de manipulaci on del compilador. Ya que cada compilador usa sus reglas de manipulaci on de nombres, el c odigo de C++ compilado con compiladores diferentes no es posible unirlo. Este hecho es importante cuando se considera usar una biblioteca de C++ precompilada. Si uno desea escribir una funci on en ensamblador que ser a usada con c odigo C++, debe conocer las reglas de manipulaci on de nombres del compilador (o usar la t ecnica explicada abajo). El estudiante astuto puede preguntar si el c odigo de la Figura 7.10 trabajar a como se espera. Ya que C++ manipula los nombres de todas las funciones, entonces la funci on printf ser a manipulada y el compilador no producir a un CALL a la etiqueta printf. Esto es un v alido, si el prototipo para printf fue colocado simplemente al principio del archivo, esto podr a pasar. El prototipo es: int printf ( const char , ...); DJGPP podr a manipular esto como printf FPCce (la F es por funci on, P por apuntador, C por constante, c por char y e por elipsis ). Esto podr a no llamar la funci on printf de la biblioteca de C. Claro est a, debe haber alguna forma para el c odigo de C++ llamar c odigo de C. Esto es muy importante ya que hay una gran cantidad de c odigo de C u til. Adem as permitirle a uno llamar c odigo de C heredado, C++ tambi en le permite a uno llamar c odigo de ensamblador usando las convenciones normales de llamado. C++ extiende la palabra clave extern para permitir especicar que la funci on o variable global que modica usa las convenciones de llamado de C. En la terminolog a de C++, las variables y funciones globales usan el encadenamiento de C. Por ejemplo para declarar que printf tenga el encadenamiento de C, use el prototipo: extern C int printf ( const char , ... ); 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. Sin embargo, haciendo esto, la funci on printf no se puede sobrecargar. Esto suministra una manera f acil de interfazar C++ y ensamblador , denir la funci on para que use el encadenamiento de C y la convenci on de llamado de C. Por conveniencia C++ tambi en permite el encadenamiento de un bloque de funciones y variables globales a ser denidas. El bloque se enmarca por las t picas llaves: extern C { / encadenamiento tipo C a variables globales y prototipos de funciones / }
de tipos de los argumentos. Las reglas para este proceso est an m as all a del alcance de este libro. Consulte un libro de C++ para los detalles.

156 void f ( int & x ) { x++; } int main() { int y = 5; f (y );

CAP ITULO 7. ESTRUCTURAS Y C++ // el & denota una referencia

// se pasa la referencia a y , obeserve que // no hay & aac printf ( %d\n, y); // imprime 6! return 0;

} Figura 7.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, en ellos se encontr a al principio de cada archivo de cabecera: #ifdef cplusplus extern C { #endif Y una construcci on parecida cerca del nal conteniendo el n de la llave. Los compiladores de C++ denen el macro cplusplus (con dos guiones bajos adelante). La porci on de c odigo anterior encierra todo el archivo de cabecera en un bloque extern C si el archivo de cabecera es compilado como C++, pero no hace nada si es compilado como C (ya que el compilador de C dar a un error de sintaxis para extern C). Esta misma t ecnica 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++.

7.2.2.

Referencias

Las referencias son otra caracter stica nueva C++. Ellas le permiten a uno pasar par ametros a funciones sin usar apuntadores expl citamente. Por ejemplo, considere el c odigo de la Figura 7.11. Actualmente los par ametros por referencia son muy simples, ellos realmente son tan solo apuntadores. El compilador solo le oculta esto al programador (tal como los compiladores de Pascal implementan los par ametros var como apuntadores). Cuando el compilador genera el ensamblador para el llamado a la funci on en la l nea 7, pasa la direcci on de y. Si uno escribi o la funci on f en ensamblador, ella

7.2. ENSAMBLADOR Y C++ se deber a comportar como si el prototipo fuera6 : void f ( int xp);

157

Las referencias son s olo una conveniencia que es especialmente u til para la sobrecarga de operadores. Esta es otra caracter stica de C++ que le permite a uno denir el signicado para los operadores en tipos de estructuras o clases. Por ejemplo, un uso com un es denir el operador m as (+) para unir objetos tipo cadena. As , si a y b fueran cadenas, a + b deber a retornar la uni on de las cadenas a y b, C++ actualmente llama una funci on para hacer esto (de hecho, esta expresi on podr a ser reescrita en la notaci on de funci on como operator +(a,b)). Por eciencia uno podr a querer pasar la direcci on de los objetos cadena en lugar de pasarlos por valor. Sin referencias esto se podr a hacer como operator +(&a,&b), pero esto requerir a escribir en la sintaxis del operador como &a + &b. Esto ser a muy complicado y confuso. Sin embargo, usando referencias, uno puede escribir a + b que se ve muy natural.

7.2.3.

Funciones inline

Las funciones inline son otra caracter stica de C++7 . Las funciones inline son hechas para reemplazar, los macros basados en el procesador que toman par ametros que son muy propensos a tener errores. Recuerde de C, que escribir un macro que eleva al cuadrado un n umero podr a verse como: #dene SQR(x) ((x)(x)) Porque el preprocesador no entiende C hace simples sustituciones, se requieren los par entesis para calcular la respuesta correcta en la mayor a de los casos. Sin embargo, a un esta versi on no dar a la respuesta correcta para SQR(X++). Los macros se usan porque ellos eliminan el trabajo extra de hacer un llamad a funci on para una funci on elemental. Como se demostr o en el cap tulo de subprogramas realizar un llamado a funci on involucra varios pasos. Por cada funci on elemental, el tiempo que se toma en llamar la funci on puede ser mayor que el tiempo necesario en realizar las operaciones en la funci on. La funci on inline es una manera mucho m as amigable de escribir c odigo que se vea como una funci on normal, pero que no hace el bloque de c odigo para CALL. En lugar de ello los llamados a funciones inline son reemplazados por el c odigo que realiza la funci on. C++ le permite a una funci on ser inline colocando la palabra inline clave al frente de la denici on de la funci on.
Claro est a ella podr a esperar declarar la funci on con encadenamiento C para evitar la manipulaci on de nombres discutida en la secci on 7.2.1 7 Los compiladores de C a menudo soportan esta caracter stica como una extensi on de ANSI C
6

158 inline int inline f ( int x ) { return xx; } int f ( int x ) { return xx; } int main() { int y , x = 5; y = f(x ); y = inline f (x ); return 0; }

CAP ITULO 7. ESTRUCTURAS Y C++

Figura 7.12: ejemplo inline Por ejemplo considere las funciones declaradas en la Figura 7.12. El llamado a la funci on f en la l nea 10 hace un llamado normal a una funci on (en ensamblador, asumiendo que x est a en la direcci on ebp-8 y y est a en ebp-4:
1 2 3 4

push call pop mov

dword [ebp-8] _f ecx [ebp-4], eax

Sin embargo, el llamado a la funci on inline f de la l nea 11 podr a verse como:


1 2 3

mov imul mov

eax, [ebp-8] eax, eax [ebp-4], eax

En este caso hay dos ventajas de las funciones inline. Primero la funci on en l nea es r apida. No se colocan los par ametros en la pila, no se crea el macro de la pila ni se destruye, no se hace el salto. Segundo el llamado de las funciones en l nea usa menos c odigo. Este u ltimo punto es verdad para este ejemplo pero no siempre es cierto. La principal desventaja de las funciones inline es que el c odigo en l nea no se encadena y as el c odigo de una funci on en l nea debe estar disponible en todos los archivos que la usen. El ejemplo anterior prueba esto. El llamado de una funci on normal solo requiere el conocimiento de los par ametros,

7.2. ENSAMBLADOR Y C++ class Simple { public : Simple (); Simple (); int get data () const ; void set data ( int ); private : int data ; }; Simple :: Simple() { data = 0; } Simple :: Simple() { / null body / } int Simple :: get data () const { return data ; } void Simple :: set data ( int x ) { data = x; } Figura 7.13: Una clase simple de C++

159

// Construcctor por defecto // destructor // funciones miembro

// datos miembro

el tipo de valor de retorno, la convenci on de llamado y el nombre de la etiqueta para la funci on. Toda esta informaci on est a disponible en el prototipo de la funci on. Sin embargo al usar funciones inline se requiere conocer todo el c odigo de la funci on. Esto signica que si cualquier parte de una funci on en l nea se cambia, todos los archivos fuentes que usen la funci on deben ser recompilados. Recuerde que para las funciones no inline, si el prototipo cambia, a menudo los archivos que usan la funci on no necesitan ser recompilados. Por todas estas razones, el c odigo de las funciones en l nea se colocan normalmente en los archivos de cabecera. Esta pr actica es contraria a la regla normal dura y r apida de C que las instrucciones de c odigo ejecutable nunca se colocan en los archivos de cabecera.

7.2.4.

Clases

Una clase en C++ describe un tipo de objeto. Un objeto tiene como miembros a datos y funciones8 . En otras palabras, es una estructura con
8

a menudo llamados en C++ funciones miembro o m as generalmente m etodos .

160

CAP ITULO 7. ESTRUCTURAS Y C++

void set data ( Simple object , int x ) { object >data = x; } Figura 7.14: Versi on de C de Simple::set data()
1 2 3 4 5 6 7 8 9 10

_set_data__6Simplei: push ebp mov ebp, esp mov mov mov leave ret eax, [ebp + 8] edx, [ebp + 12] [eax], edx

; nombre manipulador

; eax = apuntador al objeto (this) ; edx = par ametro entero ; data tiene un desplazamiento de 0

Figura 7.15: Salida del compilador para Simple::set data( int ) datos y funciones asociadas a ella. Considere la clase denida en la Figura 7.13. Una variable de tipo Simple se podr a ver tal como una estructura normal en C con un solo int como miembro. Las funciones no son almacenadas en la memoria asignada a la estructura. Sin embargo las funciones miembro son diferentes de las otras funciones . Ellas son pasadas como un par ametro oculto. Este par ametro es un apuntador al objeto al cual la funci on pertenece. Por ejemplo considere el m etodo set data de la clase simple de la Figura 7.13. Si se escribi o en C se ver a como una funci on que fue pasada expl citamente un apuntador al objeto. Como muestra la Figura 7.14. La opci on -S en el compilador DJGPP (y en los compiladores gcc y Borland tambi en) le dice al compilador producir un archivo ensamblador conteniendo el lenguaje ensamblador equivalente para el c odigo producido. Para DJGPP y gcc el archivo ensamblador naliza con una extensi on o .s y desafortunadamente usa la sintaxis del lenguaje ensamblador AT&T que es diferente de la sintaxis de NASM y MASM. 9 (Los compiladores de Borland y Microsoft
9 El sistema de compilador gcc incluye su propio ensamblador llamado gas . El ensamblador gas usa la sintaxis AT&T y as el compilador produce el c odigo en el formato de gas. Hay varias p aginas web que discuten las diferencias entre los formatos INTEL y AT&T. Hay tambi en un programa m nimo llamado a2i (http://www.multimania.com/placr/a2i.html), que convierte del formato AT&T al for-

Ahora, C++ usa la palabra clave this para acceder al apuntador de un objeto que est a dentro de la funci on miembro.

7.2. ENSAMBLADOR Y C++

161

generan un archivo con extensi on .asm usando la sintaxis MASM). La Figura 7.15 muestra la salida de DJGPP convertida a la sintaxis de MASM con comentarios a nadidos para claricar el prop osito de las instrucciones. En la primera l nea, observe que el m etodo set data se le asigna la etiqueta manipulada que codica el nombre del m etodo, el nombre de la clase y los par ametros. El nombre de la clase se codica porque otras clases podr an tener un m etodo llamado set data y los dos m etodos deben tener etiqueta diferente. Los par ametros se codican tal que la clase pueda sobre cargar el m etodo set data para tomar otros par ametros como es normal en las funciones C++. Sin embargo, tal como antes, cada compilador codicar a esta informaci on diferente en la etiqueta manipulada. Luego en las l neas 2 y 3, aparece el pr ologo t pico de la funci on. En la l nea 5 se almacena el primer par ametro de la pila en EAX. Este no es el par ametro X, es el par ametro oculto. 10 que apunta al objeto sobre el cual act ua. La l nea 6 almacena el par ametro x en EDX y la l nea 7 almacena EDX en la palabra doble a la que apunta EAX. Esto es el miembro data del objeto Simple sobre el cual act ua, que es el u nico dato en la clase, se almacena con un desplazamiento cero en la estructura Simple. Ejemplo Esta secci on usa las ideas del cap tulo para crear una clase de C++ que representa un entero sin signo de tama no arbitrario. Ya que el entero puede ser de cualquier tama no, ser a almacenado en un arreglo de enteros sin signo (palabras dobles). Se puede hacer de cualquier tama no usando memoria din amica. Las palabras dobles se almacenan en orden inverso. 11 (la palabra doble menos signicativa es la palabra doble que est a en la posici on 0). 12 La Figura 7.16 muestra la denici on de la clase Big int. . El tama no no del arreglo sin signo que es usado de un Big int se mide por el tama para almacenar este dato. 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. Para simplicar este ejemplo, s olo instancias objeto con el mismo tama no se pueden sumar o restar la una de la otra. La clase tiene tres constructores: El primero (l nea 9) inicia la instancia de la clase usando un entero sin signo normal; el segundo (l nea 18) inicia la instancia usando una cadena que contiene un valor en hexadecimal. El
mato NASM 10 Como es normal nada est a oculto en el c odigo ensamblador 11 Por qu e? Porque las operaciones de adici on siempre se comenzar an a procesar al inicio del arreglo y se mover a hacia adelante. 12 Vea el c odigo fuente example para el c odigo completo de este ejemplo. El texto solo se referir a a alguna parte del c odigo.

162

CAP ITULO 7. ESTRUCTURAS Y C++

class Big int { public : / aParmetros: size n tamao del entero expresado como el u nmero de unsigned int normales initial value valor inicial de Big int como un unsigned int normal / explicit Big int ( size t size , unsigned initial value = 0); / aParmetros size n tamao del entero expresado como el u nmero de unsigned int normales initial value valor inicial de Big int como la cadena que almacena la o representacin hexadecimal del valor . / Big int ( size t size , const char initial value ); Big int ( const Big int & big int to copy ); Big int (); // retorna el n tamao de Big int (en etrminos de unsigned int ) size t size () const; const Big int & operator = ( const Big int & big int to copy ); friend Big int operator + ( const Big int & op1, const Big int & op2 ); friend Big int operator ( const Big int & op1, const Big int & op2); friend bool operator == ( const Big int & op1, const Big int & op2 ); friend bool operator < ( const Big int & op1, const Big int & op2); friend ostream & operator << ( ostream & os, const Big int & op ); private : size t size ; // n tamao del arreglo sin signo unsigned number ; // apuntador al arreglo sin singno que // almacena el valor }; Figura 7.16: Denici on de la clase Big int

7.2. ENSAMBLADOR Y C++

163

// Prototipos para las rutinas en ensamblador extern C { int add big ints ( Big int & res , const Big int & op1, const Big int & op2); int sub big ints ( Big int & res , const Big int & op1, const Big int & op2); } inline Big int operator + ( const Big int & op1, const Big int & op2) { Big int result (op1. size ()); int res = add big ints ( result , op1, op2); if ( res == 1) throw Big int :: Overow (); if ( res == 2) throw Big int :: Size mismatch(); return result ; } inline Big int operator ( const Big int & op1, const Big int & op2) { Big int result (op1. size ()); int res = sub big ints ( result , op1, op2); if ( res == 1) throw Big int :: Overow (); if ( res == 2) throw Big int :: Size mismatch(); return result ; } Figura 7.17: C odigo de la aritm etica de la clase Big int

164

CAP ITULO 7. ESTRUCTURAS Y C++

tercer constructor (l nea 21) es el constructor copia. Esta discusi on se enfoca en c omo trabajan los operadores de la suma y de la resta ya que es all donde se usa el lenguaje ensamblador. La Figura 7.17 muestra las partes relevantes del archivo de encabezado para estos operadores. Ellas muestran c omo los operadores se usab para llamar las rutinas en ensamblador. Ya que cada compilador usa sus reglas de manipulaci on de nombres radicalmente diferentes para las funciones de los operadores, las funciones en l nea de los operadores se usan para establecer el encadenamiento de rutinas en ensamblador. Esto hace relativamente f acil portar a diferentes compiladores y es tan r apido como los llamados directos. Esta t ecnica tambi en elimina la necesidad de lanzar una excepci on desde ensamblador. Por qu e se usa el ensamblador ac a despu es de todo? Recuerde que realiza aritm etica de precisi on m ultiple, el carry se debe mover de una palabra doble para sumar la siguiente palabra doble. C++ (y C) no le permiten al programador acceder a la bandera de carry de la CPU. Realizar la suma podr a solo hacerlo recalculando la bandera de carry y condicionalmente sumar esto a la siguiente palabra doble. Es mucho m as eciente escribir el c odigo en ensamblador donde se puede acceder a la bandera de carry y usando la instrucci on ADC que autom aticamente suma la bandera de carry. Por brevedad, s olo se discutir a ac a la rutina de ensamblador add big ints. Sigue el c odigo de esta rutina (de big math.asm): big math.asm segment .text global add_big_ints, sub_big_ints %define size_offset 0 %define number_offset 4 %define EXIT_OK 0 %define EXIT_OVERFLOW 1 %define EXIT_SIZE_MISMATCH 2 ; Par ametros para las rutinas add y sub %define res ebp+8 %define op1 ebp+12 %define op2 ebp+16 add_big_ints: push mov push push

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19

ebp ebp, esp ebx esi

7.2. ENSAMBLADOR Y C++


20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61

165

push edi ; ; primero esi se~ nala a op1 ; edi se~ nala a op2 ; ebx se~ nala a res mov esi, [op1] mov edi, [op2] mov ebx, [res] ; ; Asegurarse que todos los 3 Big_Int tienen el mismo tama~ no ; mov eax, [esi + size_offset] cmp eax, [edi + size_offset] jne sizes_not_equal ; op1.size_ != op2.size_ cmp eax, [ebx + size_offset] jne sizes_not_equal ; op1.size_ != res.size_ mov ecx, ; ; ahora, los ; esi = ; edi = ; ebx = ; mov ebx, mov esi, mov edi, eax ; ecx = size of Big_ints

registrosse~ nalan a sus respectivos arreglos op1.number_ op2.number_ res.number_ [ebx + number_offset] [esi + number_offset] [edi + number_offset] ; borra bandera de carry ; edx = 0

clc xor edx, edx ; ; addition loop add_loop: mov eax, [edi+4*edx] adc eax, [esi+4*edx] mov [ebx + 4*edx], eax inc edx loop add_loop jc ok_done: xor eax, eax overflow

; no altera la bandera de carry

; valor de retorno = EXIT_OK

166
62 63 64 65 66 67 68 69 70 71 72 73

CAP ITULO 7. ESTRUCTURAS Y C++

jmp done overflow: mov eax, EXIT_OVERFLOW jmp done sizes_not_equal: mov eax, EXIT_SIZE_MISMATCH done: pop edi pop esi pop ebx leave ret big math.asm Con la esperanza que la mayor a del c odigo fuera claro al lector por ahora. Las l neas 25 a 27 almacenan los apuntadores a los objetos Big int pasados a la funci on en registro. Recuerde que las referencias realmente son solo apuntadores. Las l neas 31 a 35 aseguran que los tama nos de los 3 objetos de los arreglos sean iguales (Observe que el desplazamiento de size se a nade al apuntador para acceder al dato miembro). 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. (Una vez m as, el desplazamiento de number se a nade al apuntador del objeto). El bucle en las l neas 52 a 57 suma los enteros almacenados en los arreglos sumando primero la palabra doble menos signicativa, luego la siguiente palabra doble y as sucesivamente. La suma se realiza en esta secuencia para la aritm etica de precisi on extendida (vea la Secci on 2.1.5). La l nea 59mexamina el desborde, si hay desborde la bandera de carry se deber a jar con la u ltima suma de la palabra doble m as signicativa. Ya que las palabras dobles en el arreglo se almacenan en el orden de little endian, el bucle comienza en el inicio del arreglo y se mueve adelante hasta el nal. La Figura 7.18 muestra un corto ejemplo del uso de la clase Big int. Observe que las constantes Big int se deben declarar expl citamente como en la l nea 16. Esto es necesario por dos razones. Primero, no hay un construcctor de conversi on que convierta un unsigned int en un Big int. Segundo, solo Big Int del mismo tama no se pueden sumar, esto hace la conversi on problem atica ya que podr a ser dif cil conocer qu e tama no convertir a. Una implementaci on m as sosticada de la clase deber a permitir sumar objetos de cualquier tama no. El autor no desea complicar este ejemplo implementandolo ac a. (sin embargo exhorta al lector a hacer esto).

7.2.5.

Herencia y polimorsmo

La herencia le permite a una clase heredar los datos y m etodos de otra.

7.2. ENSAMBLADOR Y C++

167

#include big int.hpp #include <iostream> using namespace std; int main() { try { Big int b(5,8000000000000a00b); Big int a(5,80000000000010230); Big int c = a + b; cout << a << + << b << = << c << endl; for ( int i =0; i < 2; i ++ ) { c = c + a; cout << c = << c << endl; } cout << c1 = << c Big int(5,1) << endl; Big int d (5, 12345678); cout << d = << d << endl; cout << c == d << (c == d) << endl; cout << c > d << (c > d) << endl; } catch( const char str ) { cerr << Caught: << str << endl; } catch( Big int :: Overow ) { cerr << Overow << endl; } catch( Big int :: Size mismatch ) { cerr << Size mismatch << endl; } return 0; } Figura 7.18: Uso simple de Big int

168

CAP ITULO 7. ESTRUCTURAS Y C++

#include <cstddef> #include <iostream> using namespace std; class A { public : void cdecl m() { cout << A::m() << endl; } int ad; }; class B : public A { public : void cdecl m() { cout << B::m() << endl; } int bd; }; void f ( A p ) { p>ad = 5; pem(); } int main() { A a; B b; cout << nTamao de a: << sizeof(a) << Desplazamiento de ad: << osetof(A,ad) << endl; cout << nTamao de b: << sizeof(b) << Desplazamiento de ad: << osetof(B,ad) << Desplazamiento de bd: << osetof(B,bd) << endl; f(&a); f(&b); return 0; } Figura 7.19: Herencia simple

7.2. ENSAMBLADOR Y C++


1 2 3 4 5 6 7 8 9 10 11

169 ; nombre de la funci on manipulador

_f__FP1A: push mov mov mov mov push call add leave ret

ebp ebp, esp eax, [ebp+8] dword [eax], 5 eax, [ebp+8] eax _m__1A esp, 4

; eax apunta al objeto ; usar desplazamiento 0 para ad ; pasar la direcci on del objeto a A::m() ; nombre del m etodo manipulado para A::m()

Figura 7.20: C odigo en ensamblador para la herencia simple class A { public : virtual void int ad; };

cdecl m() { cout << A::m() << endl; }

class B : public A { public : virtual void cdecl m() { cout << B::m() << endl; } int bd; }; Figura 7.21: Herencia polim orca Por ejemplo consider el c odigo de la Figura 7.19. Ella muestra dos clases A y B, donde la clase B hereda de A. La salida del programa es: Tama~ no de a: 4 Desplazamiento de ad: 0 Tama~ no 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 an en el mismo desplazamiento. Esto es importante ya que la funci on f puede ser pasada como un apuntador, a un objeto A de cualquier tipo derivado de A. La Figura 7.20 muestra el c odigo asm (editado) para la funci on (generada por gcc ). Observe que en la salida del m etodo m de A fue llamado por los objetos

170
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16

CAP ITULO 7. ESTRUCTURAS Y C++

?f@@YAXPAVA@@@Z: push ebp mov ebp, esp mov mov mov mov mov push call add pop ret eax, [ebp+8] dword [eax+4], 5 ecx, [ebp + 8] edx, [ecx] eax, [ebp + 8] eax dword [edx] esp, 4 ebp

; p->ad = 5; ; ; ; ; ; ; ecx = p edx = apuntador a vtable eax = p empuja apuntador "this" llama primera funci on en vtable limpia la pila

Figura 7.22: C odigo en ensamblador para la funci on f() a y b. Desde ensamblador, uno puede ver que la llamada a A::m() est a empotrado en la funci on. Para programaci on realmente orientada a objetos, el m etodo llamado depender a de qu e tipo de objeto se pasa a la funci on. Esto es conocido como polimorfismo. C++ tiene esta caracter stica apagada por defecto. Uno usa la palabra clave virtual para i habilitarla. La Figura 7.21 muestra c omo las dos clases ser an cambiadas. Nada del otro c odigo necesita ser cambiado. El polimorsmo puede implementarse de muchas maneras. Desafortunadamente la implementaci on de gcc est a en transici on al momento de escribir esto y es signicativamente m as complicada que la implementaci on inicial. Interesado en simplicar esta discusi on, el autor solo cubrir a la implementaci on del polimorsmo que usan los compiladores de Windows, Microsoft y Borland. Esta implementaci on no ha cambiado en muchos a nos y probablemente no cambie en el futuro cercano. Con estos cambios, la salida del programa cambia: Tama~ no de a: 8 Desplazamiento de ad: 4 Tama~ no de b: 12 Desplazamiento de ad: 4 Desplazamiento de bd: 8 A::m() B::m() Ahora la segunda llamada a f llama el m etodo B::m() porque es pasado a un objeto B. Esto no es lo u nico que cambia. El tama no de A es ahora 8 (y B es 12). Tambi en el desplazamiento de ad es 4 no es 0. Qu e es un

7.2. ENSAMBLADOR Y C++ class A { public : virtual void virtual void int ad; };

171

cdecl m1() { cout << A::m1() << endl; } cdecl m2() { cout << A::m2() << endl; }

class B : public A { // B hereda de A m2() public : virtual void cdecl m1() { cout << B::m1() << endl; } int bd; }; / imprime la vtable de un objeto dado / void print vtable ( A pa ) { // p ve a pa como un arreglo de palabras dobles unsigned p = reinterpret cast<unsigned >(pa); // vt ve la vtable como un arreglo de apuntadores void vt = reinterpret cast <void >(p[0]); cout << hex << oDireccin de la vtable = << vt << endl; for ( int i =0; i < 2; i ++ ) cout << dword << i << : << vt[i] << endl; // llamado a funciones virtuales de una manera // EXTREMADAMENTE no portable void ( m1func pointer)(A ); // function pointer variable m1func pointer = reinterpret cast<void ()(A)>(vt[0]); m1func pointer(pa ); // Llama al emtodo m1 a etravs de un // apuntador a una o funcin void ( m2func pointer)(A ); // function pointer variable m2func pointer = reinterpret cast<void ()(A)>(vt[1]); m2func pointer(pa ); // llamado al emtodo m2 a etravs de // un apuntador a o funcin } int main() { A a; B b1; B b2; cout << a: << endl; print vtable (&a); cout << b1: << endl; print vtable (&b); cout << b2: << endl; print vtable (&b2); return 0; } Figura 7.23: Un ejemplo m as complicado

172

CAP ITULO 7. ESTRUCTURAS Y C++

0 4 8

vtablep s ad bd b1

04

&B::m1() &A::m2() vtable

Figura 7.24: Representaci on interna de b1 desplazamiento de cero? La respuesta a esta pregunta est a relacionada con la implementaci on del polimorsmo. Una clase C++ que tiene cualquier m etodo virtual se le da un campo oculto extra que es un apuntador a un arreglo de apuntadores a m etodos. 13 . Esta tabla se llama a menudo vtable. Para las clases A y B este apuntador se almacena con un desplazamiento 0. Los compiladores de Windows siempre colocan este apuntador al inicio de la clase, en el tope del arbol de herencia. Mirando en el c odigo ensamblador (Figura 7.22) generado para la funci on f (de la Figura 7.19) para el m etodo virtual del programa, uno puede ver que la llamada al m etodo m no es a una etiqueta. La l nea 9 encuentra la direcci on de la vtable de objeto. La direcci on del objeto se empuja en la pila en la l nea 11. La l nea 12 llama al m etodo virtual ramic andose en la primera direcci on en la vtable. 14 . Esta llamada no usa una etiqueta, salta a la direcci on de c odigo apuntada por EDX. Este tipo de llamadas es un ejemplo de v nculo tard o . El v nculo tard o retraza la decisi on de qu e m etodo llamar hasta que el c odigo se est e ejecutando. Esto le permite al c odigo llamar el m etodo apropiado para el objeto. El caso normal (Figura 7.20) ja una llamada a cierto m etodo es llamado primer v nculo ya que aqu el m etodo est a atado en el tiempo de compilaci on. El lector atento se sorprender a por qu e los m etodos de la clase en la Figura 7.21 est an declarados expl citamente para usar la conveci on de llamado de usando la plabra clave cdecl. Por defecto Microsoft usa una conveci on de llamado diferente a la convenci on normal de C para los m etodos de las clases de C++. El pasa un apuntador al objeto sobre el que act ua el m etodo en el registro ECX en lugar de usar la pila. La pila se sigue usando para los otros par ametros expl citos del m etodo. El
13 Para las clases sin m etodos virtuales los compiladores de C++ siempre hacen la clase compatible con una estructura normal de C con los mismos miembros. 14 Claro est a, este valor ya est a en el registro ECX el fue colocado en la l nea 8 y la l nea 10 pod a ser quitado y la pr oxima l nea cambiada para empujar ECX. El c odigo no es muy eciente porque se genera sin tener activadas las optimizaciones del compilador.

7.2. ENSAMBLADOR 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.25: Salida del programa de la Figura 7.23

173

modicador cdecl le dice usar la convenci on de llamado est andar de C. Borland C++ usa la convenci on de llamado de C por defecto. Ahora miraremos un ejemplo un poco m as complicado (Figura 7.23). En el, las clases A y B cada una tiene dos m etodos m1 y m2.Recuerde que ya que la clase B no dene su propio m etodo m2, hereda el m etodo de la clase A. La Figura 7.24 muestra como un objeto b se ve en memoria. La Figura 7.25 muestra la salida del programa. Primero observe la direcci on de vtable para cada objeto. Las dos direcciones de los objetos B son iguales y as , ellas compartan la misma vtable. Una vtable es una propiedad de la clase no un objeto (como un miembro static). Ahora mire en las direcciones en las vtables. 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). Los apuntadores al m etodo m2 son los mismos vtables de las clases A y B porque la clase B hereda el m etodo m2 de la clase A. Las l neas 25 a 32 muestran como uno podr a llamar a una funci on virtual leyendo su direcci on de la vtable para el objeto. 15 . La direcci on del m etodo se almacena en un apuntador a funci on de tipo C con un apuntador expl cito
15

Recuerde que este c odigo solo trabaja con los compiladores de Borland y MS, no para

gcc.

174

CAP ITULO 7. ESTRUCTURAS Y C++

this. De la salida en la Figura 7.25, uno puede ver que trabaja. Sin embargo, por favor no escriba c odigo como este. Es solo para ilustrar c omo los m etodos virtuales usan la vtable. Hay algunas lecciones pr acticas para aprender de esto. Un hecho importante es que uno tendr a que tener mucho cuidado cuando lee y escribe variables de una clase a un archivo binario. Uno no puede s oo usar una lectura o escritura binaria en un objeto completo ya que esto podr a leer o escribir la apuntador vtable al archivo. Este es un apuntador a donde la vtable reside en la memoria del programa y variar a de programa a programa. Este mismo problema puede ocurrir con estructuras, pero en C, las estructuras s olo tienen apuntadores en ellas si el programador las coloca expl citamente en ellas. No hay apuntadores obvios denidos en las clases A o B. Otra vez, es importante tener en cuenta que cada compilador implementa los m etodos virtuales a su manera. En Windows, los objetos de las clases COM (Component Object Model) usan las vtables para implementar interfaces COM16 . S olo los compiladores que implementan m etodos virtuales como Microsoft pueden crear clases COM. Esta es la raz on por la cual Borland usa la misma implementaci on que Microsoft y una de las principales razones por las cuales gcc no se puede usar para crear clases COM. El c odigo para el m etodo virtual parece ex actamente como un m etodo no virtual. S olo el c odigo que llama es diferente. Si el compilador puede estar absolutamente seguro de qu e m etodo virtual ser a llamado, puede ignorar la vtabley llamar el m etodo directamente, (usando el primer v nculo).

7.2.6.

Otras caracter sticas de C++

Las otras caracter sticas de C++ (informaci on en tiempo de ejecuci on, manejo de excepciones y herencia m ultiple) est an m as all a del alcance de este texto. Si el lector desea avanzar m as, un buen punto de partida es The Annotated C++ Reference Manual por Ellis and Stroustrup y The Design and Evolution of C++ por Stroustrup.

16 Las clases COM tambi en usan la convenci on de llamado code de C.

stdcall no la normal

Ap endice A

Instrucciones del 80x86


A.1. 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. Por ejemplo el formato R,R signica que la instrucci on toma dos operandos de registro. Muchas de las instrucciones con dos operandos permiten los mismos operandos. La abreviaci on 02 se usa para representar estos operandos: R,R R,M R,I M,R M,I. Si se puede usar como operando un registro de 8 bits o la memoria, se usa la abreviaci on R/M8 La tabla tambi en muestra c omo se afectan varias de las banderas del registro FLAGS. Si la columna est a en blanco, el bit correspondiente no se afecta. Si el bit siempre cambia a alg un valore en particular, se muestra un 1 o un 0 en la columna. Si el bit cambia a un valor que depende del operando de la instrucci on se coloca un C en la columna. Finalmente, si el bit es modicado de alguna manera no denida se coloca un ? en la columna. Ya 175

176

APENDICE A. INSTRUCCIONES DEL 80X86

que las u nicas instrucciones que cambian las banderas de direcci on son CLD y STD, no se listan bajo la columna de FLAGS. Banderas S Z A P C C C C C C C C C C ? C

Nombre ADC ADD AND BSWAP CALL CBW CDQ CLC CLD CMC CMP CMPSB CMPSW CMPSD CWD CWDE DEC DIV ENTER IDIV IMUL

Descripci on 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 on Complementa el carry Compara enteros Compara Bytes Compara Words Compara Dwords Convierte Word a Dword en DX:AX Convierte Word a Dword en EAX Decrementa entero Divisi on sin signo Hace el marco de la pila Divisi on con signo Multiplicaci on con signo

Formatos O2 O2 O2 R32 RMI

O C C 0

C C C 0

O2

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

RM RM I,0

C ?

C ?

C ?

C ?

C ?

RM ? R M C R16,R/M16 R32,R/M32 R16,I R32,I


R16,R/M16,I R32,R/M32,I

? ?

? ?

? ?

? ?

? C

INC INT JA

Incremento entero Genera una interrupci on Salta si est a sobre

RM I I

A.1. INSTRUCCIONES PARA ENTEROS

177 Banderas S Z A P

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

Descripci on Salta si est a sobre o es igual Salta si est a bajo Salta si est a bajo o es igual 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 a sobre Salta si no est a sobre o es igual Salta si no est a bajo Salta si no est a bajo o es igual Salta si no hay carry Salta si no es igual Salta si no es mayor Salta si no es mayor o es igual Salta si no es menor Salta si no es menor o igual Salta si no hay desborde Salta si es positivo Salta si no es cero Salta si hay desborde Salta si hay paridad par Salta si hay paridad impar Salta si hay signo Salta si hay cero

Formatos I I I I I I I I I I RMI I I i I I I I I I I I I I I I I I I

178

APENDICE A. INSTRUCCIONES DEL 80X86 Banderas S Z A P

Nombre LAHF LEA LEAVE LODSB LODSW LODSD LOOP LOOPE/LOOPZ LOOPNE/LOOPNZ MOV

Descripci on Carga FLAGS en AH Carga direcci on efectiva Abandona el marco de la pila Carga byte Carga palabra Carga palabra doble bocle Bucle si es igual Bucle si no es igual Mueve datos

Formatos R32,M

I I I O2 SR,R/M16 R/M16,SR

MOVSB MOVSW MOVSD MOVSX

Mueve Mueve Mueve Mueve

byte palabra palabra doble con signo

MOVZX

Mueve sin signo

MUL NEG NOP NOT OR POP POPA POPF PUSH PUSHA PUSHF RCL

Multiplicaci on sin signo Negaci on 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 Rota a la izquierda con carry

R16,R/M8 R32,R/M8 R32,R/M16 R16,R/M8 R32,R/M8 R32,R/M16 RM C RM RM O2 R/M16 R/M32 C

? C

? C

? C

? C

C C

C R/M16 R/M32 I

R/M,I R/M,CL

A.1. INSTRUCCIONES PARA ENTEROS

179 Banderas S Z A P

Nombre RCR REP REPE/REPZ REPNE/REPNZ RET ROL 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

Descripci on Rota a la derecha con carry Repite Repite si es igual Repite si no es igual Retorno Rota a la izquierda Rota a la derecha Copia AH en FLAGS Dezplazamiento a la izquierda Resta con pr estamo busca un Byte Busca una palabra Busca una palabra doble ja sobre ja sobre o igual ja bajo ja bajo o igual ja el carry ja igual ja mayor ja mayor o igual ja menor ja menor o igual ja no sobre ja no sobre o igual ja no bajo ja no bajo o igual ja no carry ja no igual ja no mayor ja no mayor o igual ja no menor jan no menor o igual ja no desborde ja no signo

Formatos R/M,I R/M,CL

O C

C C

R/M,I R/M,CL R/M,I R/M,CL R/M,I R/M, CL O2

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

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

180

APENDICE A. INSTRUCCIONES DEL 80X86 Banderas S Z A P

Nombre SETNZ SETO SETPE SETPO SETS SETZ SAR SHR SHL STC STD STOSB STOSW STOSD SUB TEST XCHG XOR

Descripci on ja no cero ja desborde ja paridad par ja paridad impar ja signo ja cero Desplazamiento aritm etico a la derecha Desplazamiento l ogico a la derecha Desplazamiento l ogico a la izquierda ja el carry ja la bandera de direcci on almacena byte almacena palabra almacena palabra doble resta comparaci on l ogica Intercambio XOR entre bits

Formatos R/M8 R/M8 R/M8 R/M8 R/M8 R/M8 R/M,I R/M, CL R/M,I R/M, CL R/M,I R/M, CL

C C C 1

O2 R/M,R R/M,I R/M,R R,R/M O2

C 0

C C

C C

C ?

C C

C 0

A.2. INSTRUCCIONES DE PUNTO FLOTANTE

181

A.2.

Instrucciones de punto otante

En esta secci on, se describen muchas de las instrucciones del coprocesador matem atico del 80x86. La secci on de descripci on describe brevemente la operaci on de la instrucci on. Para ahorrar espacio, la informaci on sobre si la instrucci on saca de la pila no se da en la descripci on. La columna de formato muestra que tipo de operandos se pueden suar con cada instrucci on. Se usan las siguientes abreviaturas STn F D E I16 I32 I64 Un registro del coprocesador n umero de precisi on simple en memoria n umero de precisi on doble en memoria n umero de precisi on extendida en memoria palabra entera en memoria palabra doble entera en memoria palabra cu adruple entera en memoria

Las instrucciones que requieren un Pentium Pro o posterior est an mar* cadas con un asterisco ( ). Instrucci on FABS FADD src FADD dest, ST0 FADDP dest [,ST0] FCHS FCOM src FCOMP src FCOMPP src FCOMI* src FCOMIP* src FDIV src FDIV dest, ST0 FDIVP dest [,ST0] FDIVR src FDIVR dest, ST0 FDIVRP dest [,ST0] FFREE dest FIADD src FICOM src FICOMP src FIDIV src FIDIVR src Descripci on ST0 = |ST0| ST0 += src dest += STO dest += ST0 ST0 = ST0 Compara ST0 y src Compara ST0 y src Compara ST0 y ST1 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 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

182 Instrucci on FILD src FIMUL src FINIT FIST dest FISTP dest FISUB src FISUBR src FLD src FLD1 FLDCW src FLDPI FLDZ FMUL src FMUL dest, ST0 FMULP dest [,ST0] FRNDINT FSCALE FSQRT FST dest FSTP dest FSTCW dest FSTSW dest FSUB src FSUB dest, ST0 FSUBP dest [,ST0] FSUBR src FSUBR dest, ST0 FSUBP dest [,ST0] FTST FXCH dest

APENDICE A. INSTRUCCIONES DEL 80X86 Descripci on Push src on Stack ST0 *= src Inicia el coprocesador almacena ST0 almacena ST0 ST0 -= src ST0 = src - ST0 empuja src en la pila empuja 1.0 en la pila Load Control Word Register empuja en la pila empuja 0.0 en la pila ST0 *= src dest *= STO dest *= ST0 Redondea ST0 ST0 = ST0 2 ST1 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.0 Intercambia ST0 y dest Formato I16 I32 I64 I16 I32 I16 I32 I16 I32 I64 I16 I32 I16 I32 STn F D E I16

STn F D STn STn

STn F D STn F D E I16 I16 AX STn F D STn STn STn F D STn STn STn

Indice alfab etico


, 12 tipos de almacenamiento automatic, 96 register, 96 volatile, 96 ADC, 39, 56 ADD, 13, 39 AND, 52 archivo de listado, 2526 archivo esqueleto, 26 Aritm etica de complemento a dos, 35 array1.asm, 101105 arreglos, 97118 acceso, 99105 deninir est atico, 97 variable local, 98 denir, 9798 multidimensional, 108 dos dimensiones, 105107 par ametros, 108 multidimensionales, 105 bandera de paridad, 41 binario, 12 suma, 2 BSWAP, 62 bucle do while, 45 bucle while, 45 byte, 4 C driver, 19 C++, 153174 183 clases, 159174 ejemplo Big int, 161166 encadenamiento seguro de tipos, 154 extern C , 155156 funciones inline, 157159 funciones miembro, v ease m etodos herencia, 166174 manipulaci on de nombres, 153 156 polimorsmo, 169174 primer v nculo, 172 referencias, 156157 v nculo tard o, 172 vtable, 172174 CALL, 7172 CBW, 33 CDQ, 33 CLC, 39 CLD, 109 CMP, 40 CMPSB, 111, 112 CMPSD, 111, 112 CMPSW, 111, 112 codigo de operaci on, 11 COM, 174 Compilador Watcom, 86 compilador, 5, 12 Borland, 23, 24 DJGPP, 23, 24 gcc, 23 attibute , 86

184 attribute , 147, 148, 152 Microsoft, 23 embalado pragma, 147 pragma pack, 148, 152 compiler gcc attribute , 151 Microsoft pragma pack, 151 complemento a dos, 3032 aritm etica, 39 constructor copia, 164 contando bits, 6266 m etodo dos, 6365 m etodo tres, 6566 m etodo uno, 6263 convenci on de llamado, 80, 8687 cdecl, 87 stdcall, 87 C, 22, 74, 8387 etiquetas, 85 etiquetas , 84 par ametros, 85 param etros, 85 registros, 84 retorno de valores, 86 valores de retorno, 86 llamado estandar, 87 Pascal, 74 registro, 87 stdcall, 87, 174 convenciones de llamado, 67, 72 stdcall, 74 coprocesador de punto otante, 127 142 carga y almacenamiento de datos, 128 cargar y almacenar datos, 128 comparaciones, 131132 hardware, 127 multiplicaci on and divisi on, 130 131

INDICE ALFABETICO suma y resta, 129130 CPU, 57 80x86, 6 CWDE, 33 decimal, 1 depuraci on, 1819 direccionamiento indirecto, 6768 arreglos, 101105 directiva, 1416 %dene, 14 DX, 97 datos, 1516 equ, 14 extern, 80 global, 22, 83 RESX, 98 TIMES, 97 directive global, 81 DIV, 36, 50 ejecuci on especulativa, 56 encadenando, 24 encadenar, 24 endianess, hyperpage60, 26 62 invert endian, 62 ensamblador, 12 enteros, 2940 bit de signo, 29, 33 comparaciones, 40 con signo, 2932, 40 divisi on, 3637 extensi on de signo, 35 extensi on del signo, 32 multiplicaci on, 3536 precisi on extendida, 39 representaci on, 2935 complemento a dos, 30 magnitud y signo, 29 representaci on de complemento a uno, 30

INDICE ALFABETICO representaci on en complemento a dos, 32 sin signo, 29, 40 estructuras, 145153 alineamiento, 146148 campos de bit, 151 campos de bits, 148 osetof(), 146 etiqueta, 16 FABS, 133 FADD, 129 FADDP, 129 FCHS, 133 FCOM, 131 FCOMI, 132 FCOMIP, 132, 143 FCOMP, 131 FCOMPP, 131 FDIV, 131 FDIVP, 131 FDIVR, 131 FDIVRP, 131 FFREE, 128 FIADD, 129 FICOM, 131 FICOMP, 131 FIDIV, 131 FIDIVR, 131 FILD, 128 FIST, 128 FISUB, 130 FISUBR, 130 FLD, 128 FLD1, 128 FLDCW, 128 FLDZ, 128 FMUL, 130 FMULP, 130 FSCALE, 133, 144 FSQRT, 133 FST, 128 FSTCW, 128 FSTP, 128 FSTSW, 132 FSUB, 130 FSUBP, 130 FSUBR, 130 FSUBRP, 130 FTST, 131 FXCH, 128 gas, 160 GHz, 6 hexadecimal, 34

185

I/O, 1719 asm io library, 1719 dump math, 18 dump mem, 18 dump regs, 18 dump stack, 18 print char, 17 print int, 17 print nl, 17 print string, 17 read char, 17 read int, 17 IDIV, 37 immediato, 13 IMUL, 3536 instrucci on if, 44 instrucci on if , 45 instrucciones de cadena, 108118 Interfazando con C, 83 interfazando con C, 91 interrupci on, 10 JC, 42 JE, 43 JG, 43 JGE, 43 JL, 43 JLE, 43 JMP, 4041 JNC, 42

186 JNE, 43 JNG, 43 JNGE, 43 JNL, 43 JNLE, 43 JNO, 42 JNP, 42 JNS, 42 JNZ, 42 JO, 42 JP, 42 JS, 42 JZ, 42 LAHF, 132 LEA, 8586, 105 lenguaje de m aquina, 5, 11 lenguaje ensamblador, 1213 localidad, 145 LODSB, 109 LODSD, 109 LODSW, 109 LOOP, 44 LOOPE, 44 LOOPNE, 44 LOOPNZ, 44 LOOPZ, 44 m etodos, 159 MASM, 12 math.asm, 3739 memoria, 45 p aginas, 10 segmento, 10 segmentos, 9 virtual, 10 memoria:segmentos, 9 memory.asm, 113118 modo protegido 16 bits, 10 16-bits, 9 32-bits, 10 modo real, 89

INDICE ALFABETICO MOV, 13 MOVSB, 111 MOVSD, 111 MOVSW, 111 MOVSX, 33 MOVZX, 33 MUL, 3536, 50, 105 NASM, 12 NEG, 58 nibble, 4 NOT, 54 Operaciones con bits desplazamientos, 49 desplazamientos l ogicos, 49 operaciones con bits AND, 52 C, 5860 desplazamientos, 52 desplazamiento l ogicos, 50 desplazamientos aritm eticos, 5051 rotaciones, 51 ensamblador, 5455 OR, 53 XOR, 53 operciones con bits NOT, 53 OR, 53 pila, 7080 param etros, 72 variables locales, 7880, 8586 predicci on de ramicaciones, 56 prediccio on de ramicaciones, 55 prime.asm, 4648 prime2.asm, 138142 programas multim odulo, 8083 punto otante, 119142 aritm etica, 124126 representaci on, 119124 desnormalizada, 123

INDICE ALFABETICO desnormalizado, 123 IEEE, 121124 precisi on doble, 124 precisi on simple, 123 presici on simple, 122 uno oculto, 122 representaci oon precisi on doble, 124 quad.asm, 133136 RCL, 51 RCR, 51 read.asm, 136138 recursi on, 9194 registro, 5, 78 ndice, 7 32-bit, 8 apuntador a la pila, 7, 8 apuntador base, 7, 8 EDI, 109 EDX:EAX, 36, 37, 39, 86 EFLAGS, 8 EIP, 8 ESI, 109 FLAGS, 8, 40 CF, 40 DF, 109 OF, 40 SF, 40 ZF, 40 IP, 8 segmento, 7, 8, 109 registros FLAGS PF, 41 registrp IP, 8 reloj, 6 REP, 110111 REPE, 112, 113 REPNE, 112, 113 REPNZ, v ease REPNE, 112 REPZ, v ease REPE, 112 RET, 7172, 74 ROL, 51 ROR, 51

187

SAHF, 132 SAL, 50 salto condicional, 4143 SAR, 50 SBB, 39 SCASB, 111, 112 SCASD, 111, 112 SCASW, 111, 112 SCSI, 149150 segmento bss, 22 segmento de c odigo, 22 segmento de datos, 22 segmento text, v ease segmento code SETxx, 56 SETG, 57 SHL, 49 SHR, 49 stack parameters, 75 STD, 109 STOSB, 109 STOSD, 109 SUB, 13, 39 subprograma, 6896 llamado, 7180 reentrante, 9192 subrutina, v ease subprograma TASM, 12 TCP/IP, 61 TEST, 54 tipos de alamcenamiento global, 94 tipos de almacenamiento static, 94 UNICODE, 61 virtual, 170

188 word, 8 XCHG, 62 XOR, 53

INDICE ALFABETICO

You might also like