Documente Academic
Documente Profesional
Documente Cultură
Cada tipo de CPU entiende su propio lenguaje de m quina. Las instrucciones en lenguaje de mquina son n meros almacenados como bytes en memoria. Cada instruccin tiene su propio y nico cdigo llamado cdigo de operacin u opcode. Las instrucciones del procesador 80X86 varan de tamao. El opcode est siempre al inicio de la instruccin. Muchas instrucciones incluyen tambin datos (ver constantes o direcciones) usados por las instrucciones. El lenguaje de mquina es muy difcil de programar directamente. Descifrar el significado de las instrucciones codificadas numricamente es te dioso para los humanos. Por ejemplo la instruccin para sumar los registros EAX y EBX y almacenar el resultado en EAX est codificada por los siguientes cdigos hexadecimales: 03 C3 Esto no es obvio. Afortunadamente, un programa llamado ensamblador puede hacer este aburrido trabajo para el programador.
LENGUAJE ENSAMBLADOR
Un programa Escrito en lenguaje ensamblador es almacenado como texto (tal como programas de alto nivel). Cada instruccin representa exactamente una instruccin de la mquina. Por ejemplo, la instruccin de suma descrita arriba podr ser representada en lenguaje ensamblador como: Mnemonico operando(s)
dato_fuente, dato_destino
Dicho programa es muy simple pero nos permitir demostrar su traduccin a lenguaje ensamblador. Para ello, la compilacin se realizar de la siguiente forma: $ gcc -S hello.c Lo cual deber de crear un archivo de nombre hello.s (la extensin .s, proviene de la filosofa de los proyectos GNU). Una vez realizado lo anterior se procede a compilar dicho archivo en ensamblador para generar el archivo ejecutable. $ gcc -o hello.exe hello.s
Posterior a ello se tendr que realizar la ejecucin del programa correspondiente. Lo interesante de este programa est en visualizar el contenido del archivo en ensamblador obtenido despus de la compilacin correspondiente.
.text
# section declaration
# we must export the entry point to the ELF linker or .global _start # loader. They conventionally recognize _start as their # entry point. Use ld -e foo to override the default. _start:
.text .global _start _start: movl $len, %edx movl $msg, %ecx movl $1, %ebx movl $4, %eax int $0x80 movl $0, %ebx movl $1, %eax int $0x80 .data msg: .ascii "hola mundo!\n" len = 12
# write our string to stdout movl$len,%edx movl$msg,%ecx movl$1,%ebx movl$4,%eax int $0x80 # and exit movl$0,%ebx movl$1,%eax int $0x80 .data # first argument: exit code # system call number (sys_exit) # call kernel # section declaration # third argument: message length # second argument: pointer to message to write # first argument: file handle (stdout) # system call number (sys_write) # call kernel
msg: .ascii "Hello, world!\n" # our dear string len = . - msg # length of our dear str
ESTRUCTURA EN PSEUDOCDIGO
Por ms trivial que parezca el problema, siempre resulta til hacer el esquema de ejecucin a modo de pseudocdigo. De esta forma garantizamos, en primer lugar, una buena lgica de programa, y en segundo, reducir la labor de programacin a una simple traduccin de un lenguaje al cual ya estamos familiarizados. Como primer intento podramos hablar de un pseudocdigo de alto nivel de este tipo.
Ahora bien, si tenemos en cuenta que Assembler es un lenguaje donde las operaciones son predominantemente binarias, valdr la pena convertir nuestro algoritmo a uno que slo requiera operaciones de ste tipo.
GAS (Gnu ASsembler) utiliza la sintaxis de AT&T, que tiene pequeas diferencias con respecto a la sintaxis estndar de Intel (usada en NASM, TASM, MASM, etc). Las principales diferencias se detallan a continuacin:
En AT&T, el destino se coloca a la derecha y el fuente a la izquierda (en Intel es al revs). Las siguientes instrucciones cargan en ebx el valor de eax AT&T: movl %eax, %ebx INTEL: mov ebx, eax En AT&T, a los valores inmediatos se les aade el prefijo $ en el siguiente ejemplo, la primera instruccin carga la direccin de la variable en eax; la segunda carga el valor 0F02h en ebx AT&T: movl $var, %eax movl $0xf02, %ebx INTEL: mov eax, offset var mov ebx, 0f02h
En AT&T, el tamao del resultado se especifica con sufijos (b, w o l) en las instrucciones (en Intel cuando hay ambigedad se utiliza byte ptr, word ptr o dword ptr). Si lo omitimos, GAS intentar adivinar el tamao, y es algo que no queremos que haga... AT&T: movb var, %ah movw %bx, %ax movb movw movl movl INTEL: mov ah, byte ptr var mov ax, bx
INTEL: mov al,bl mov ax, bx mov eax,ebx mov eax, dword ptr [ebx]
Direccionamiento a memoria: Es uno de los aspectos que ms cambian. Veamos la sintaxis de Intel para hacer un direccionamiento a base, con ndice y desplazamiento:
Veamos dos ejemplos: AT&T: movl array (, %eax, 4), %edx INTEL: mov edx, array[eax*4]
AT&T: movl (%ebx) , %eax movl 3(%ebx) , %eax Salto lejano AT&T: lcall $seccin, $offset ljmp $seccin, $offset lret $V
Nemotcnico. Varan los nemotcnicos de algunas instrucciones AT&T: AT&T INTEL movswl %ax, %ecx movsx ecx, ax movzbw %ah, %cx movzx cx, ah cbtw cbw cwtl cwde cwtd cwd cltd cdq
Directivas del compilador. Como vimos, los programas ensamblador, adems de las instrucciones que componen el programa, contienen rdenes al compilador que le servirn para definir las secciones del programa, definir los tipos de datos, macros, etc. Como comentamos ms arriba, hay diferencias en cuanto a algunas directivas al programar con el ensamblador GAS o NASM. En ambos ensambladores hay que definir las secciones de datos y cdigo utilizando los mismos nombres (.data .bss .text). Sin embargo, la directiva utilizada para definir las secciones difiere de un ensamblador a otro:
En ambos ensambladores, la etiqueta de entrada al programa ensamblador suele ser _start. Sin embargo, la directiva utilizada difiere de un ensamblador a otro:
La definicin de datos constantes se lleva a cabo utilizando de la misma forma, pero utilizando palabras reservadas diferentes:
En el ejemplo definiremos una macro para terminar el programa, otra para mostrar una cadena por salida estndar, y otra para leer cadenas de texto desde entrada estndar.
La forma de llamar a una macro es parecida a como se llama a una funcin de C/C++ (por la forma en que le pasaremos los valores): # # # COMPILAR: as -o m.o m.s ls -o m m.o
.macro terminar movl $1,%eax movl $0,%ebx int $0x80 .endm #espera ECX=cadena ; EDX=longitud
.macro escribir_cadena cadena longitud movl $4,%eax movl $1,%ebx #stdout movl \cadena,%ecx movl \longitud,%edx int $0x80 .endm #espera ECX=cadena ; EDX=longitud .macro leer_cadena cadena longitud movl $3,%eax movl $0,%ebx #stdin movl \cadena,%ecx movl \longitud,%edx int $0x80 .data retorno: .byte 0x0A mensaje1: .ascii "\n Introduce una cadena: " longitud1 = . - mensaje1 buffer: .ascii " " .text .globl _start _start: escribir_cadena $mensaje1 $longitud1 leer_cadena $buffer $10 escribir_cadena $retorno $1 escribir_cadena $buffer $10 escribir_cadena $retorno $1 terminar
Podemos hacer uso de funciones para facilitar la programacin. Dentro de la seccin .text (y antes del punto de entrada al programa) podemos definir las diferentes funciones (subrutinas), utilizando una etiqueta que indiquen el inicio de la funcin y cuidando siempre terminar la ejecucin de sta con la instruccin ret. Una funcin tendr el siguiente aspecto: nombreFuncion: instrucciones ret Veamos el ejemplo anterior (el de las macros) utilizando tres subrutinas. Como se ver en el programa principal, el paso de parmetros a la funcin hay que hacerlo a travs de la pila o en los registros o variables globales del programa (segn como se haya programado la subrutina):
.data retorno: .byte 0x0A mensaje1: .ascii "\n Introduce una cadena: " longitud1 = . - mensaje1 buffer: .ascii " " .text .globl _start funcion_terminar: movl $1,%eax movl $0,%ebx int $0x80 ret #parmetros ECX=cadena ; EDX=longitud funcion_escribir_cadena: movl $4,%eax movl $1,%ebx #stdout int $0x80 ret #parmetros ECX=cadena ; EDX=longitud
funcion_leer_cadena: movl $3,%eax movl $0,%ebx #stdin int $0x80 ret _start: #los parmetros se pasan en los registros movl $mensaje1,%ecx movl $longitud1,%edx call funcion_escribir_cadena movl $buffer,%ecx movl $10,%edx call funcion_leer_cadena movl $retorno,%ecx movl $1,%edx call funcion_escribir_cadena movl $buffer,%ecx movl $10,%edx call funcion_escribir_cadena movl $retorno,%ecx movl $1,%edx call funcion_escribir_cadena #esta ltima no necesita ningn parmetro call funcion_terminar
NASM
Una directiva es un artificio 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 cdigo de mquina. Los usos comunes de las directivas son:
Definir constantes Definir memoria para almacenar datos en ella Definir la memoria para almacenar datos en ella Agrupar la memoria en segmentos Incluir cdigo fuente condicionalmente Incluir otros archivos
El cdigo de NASM pasa a travs de un preprocesador tal como C. Tiene muchas de las rdenes 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 definir un smbolo. Los smbolos son constantes con nombre que se pueden emplear en el programa ensamblador. El formato es: smbolo equ valor Los valores de los smbolos no se pueden redefinir posteriormente.
La directiva %define
Esta directiva es parecida a la #define de C. Se usa normalmente para definir macros tal como en C. %define SIZE 100 mov eax, SIZE El cdigo de arriba define un macro llamado size y muestra su uso en una instruccin MOV. Los macros son ms flexibles que los smbolos de dos maneras. Los macros se pueden redefinir y pueden ser ms que simples constantes numricas.
Las directivas de datos son usadas en segmentos de datos para definir espacios de memoria. Hay dos formas en que la memoria puede ser reservada. La primera es solo definir el espacio para los datos; la segunda manera define el espacio y el valor inicial. El primer mtodo usa una de las directivas RESX. La X se reemplaza con una letra que determina el tamao del objeto (u objetos) que sern almacenados. La tabla 1.3 muestra los valores posibles. El segundo mtodo (que define un valor inicial tambin) usa una de las directivas DX. Las X son las mismas que las de la directiva RESX.
Las comillas dobles o simples se interpretan igual. Las definiciones consecutivas de datos se almacenan secuencialmente en memoria. Esto es, la palabra L2 se almacena inmediatamente despus que la L1. Se pueden definir tambin 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
Veamos un ejemplo de lectura de los argumentos de lnea de comando, programado para el ensamblador GAS. En este ejemplo se hace uso de todo lo descrito en Acceso a la pila en un programa ensamblador en Linux:
.data retorno: .byte 0x0A funcion_pintar_cadena: #definicion de una funcion movl %eax,%ecx #el parametro ha sido pasado en EAX xorl %edx,%edx contar: movb (%ecx, %edx, 1),%al testb %al,%al #comprobar el caracter de la cadena jz fin_contar incl %edx #vamos incrementando el calculo en EDX jmp contar fin_contar: movl $4,%eax #una vez calculada la longitud,se movl $1,%ebx int $0x80 movl $4,%eax #mostramos el RETORNO_CARRO movl $retorno,%ecx movl $1,%edx #es un solo caracter int $0x80 ret
.text .globl _start _start: pop %eax #extraer de la pila el ARGC repetir: #bucle para recorrer todos los argumentos pop %eax #extraer el ARGV[i] testl %eax,%eax #comprobar si es NULL jz terminar call funcion_pintar_cadena #llamada a la funcion jmp repetir terminar: movl $1, %eax movl $0, %ebx int $0x80 #funcion del sistema para terminar
Para cada parmetro llamamos a una funcin que lo muestre por salida estndar. Para ello debe calcular la longitud de la cadena (argumento actual), contando uno por uno cada carcter que la forma. Tras cada argumento impreso, se hace un retorno de carro (es una cadena de caracteres de longitud 1). El programa muestra tambin el nombre del ejecutable como primer argumento (sera casi inmediato hacer que slo muestre los argumentos reales).
El siguiente ejemplo lee los 1024 primeros bytes de un fichero que le pasemos como primer argumento por la lnea de comandos y los muestra por salida estndar. En este ejemplo, la sintaxis utilizada ha sido la de Intel (NASM). section .data mensaje db 0xA,"---vamos a probar esto---",0xA longitud equ $ - mensaje mensaje2 db 0xA,"---hemos terminado---",0xA longitud2 equ $ - mensaje2 tamano equ 1024
section .bss buffer: resb1024 section .text global _start ;definimos el punto de entrada _start: mov edx,longitud ;EDX=long. de la cadena mov ecx,mensaje ;ECX=cadena a imprimir mov ebx,1 ;EBX=manejador de fichero (STDOUT) mov eax,4 ;EAX=funcin sys_write() del kernel int 0x80 ;interrupc. 80 (llamada al kernel) pop ebx pop ebx ;extraer "argc" ;extraer argv[0] (nombre del ejecutable)
pop ebx ;extraer el primer arg real (puntero a cadena) mov eax,5 ;funcin para sys_open() mov ecx,0 ;O_RDONLY (definido en fcntl.h) int 0x80 ;interrupc. 80 (llamada al kernel) test eax,eax ;comprobar si dev. error o el descriptor jns leer_del_fichero hubo_error: mov ebx,eax ;terminar, devolviendo el cdigo de error mov eax,1 int 0x80 leer_del_fichero: mov ebx,eax ;no hay error=>devuelve descriptor push ebx mov eax,3 ;funcin para sys_read() mov ecx,buffer ;variable donde guardamos lo leido mov edx,tamano ;tamao de lectura int 0x80 js hubo_error mostrar_por_pantalla: mov edx,eax ;longitud de la cadena a escribir mov eax,4 ;funcin sys_write() mov ebx,1 ;descriptor de STDOUT int 0x80
cerrar_fichero: pop ebx mov eax,6 ;funcin para cerrar un fichero int 0x80 mov edx,longitud2 ;EDX=long. de la cadena mov ecx,mensaje2 ;ECX=cadena a imprimir mov ebx,1 ;EBX=manejador de fichero (STDOUT) mov eax,4 ;EAX=funcin sys_write() del kernel int 0x80 ;interrupc. 80 (llamada al kernel) mov ebx,0 ;EBX=cdigo de salida al SO mov eax,1 ;EAX=funcin sys_exit() del kernel int 0x80 ;interrupc. 80 (llamada al kernel)
En este caso, nos vemos obligados a definir como punto de entrada del programa ensamblador, en lugar de _start, la etiqueta main (como en C/C++). Ya dentro del programa, antes de llamar a ninguna funcin, nuestro programa ensamblador (sintaxis GAS) debe inicializar la pila. Luego, para pasar los parmetros (si son necesarios), debe insertarlos en la pila en el orden inverso (primero el ltimo, etc.), hacer la llamada a la funcin, y por ltimo, deshacer las inserciones hechas antes de la llamada (ajustar el valor del registro puntero de pila, SP):
.data mensaje: .ascii " --mensaje desde ASM directamente--\n" mensaje_SIZE = . - mensaje .text .globl main main: # inicializar la pila pushl %ebp movl %esp, %ebp # llamar a la funcin que recibe 2 argumentos de tipo entero pushl $25 #segundo parametro (4 bytes) pushl $76 #primer parametro (4 bytes) call func_con_parametros #func_con_parametros(76,25) addl $8, %esp #quitar de la pila 8 bytes #(dos num. enteros) # mostrar texto directamente con la INT 80h movl $4, %eax movl $1, %ebx movl $mensaje, %ecx movl $mensaje_SIZE, %edx int $0x80
Antes de ejecutar el programa debemos establecer dos puntos de ruptura (break): uno correspondiente a la etiqueta de comienzo del programa (_start) y otro en la lnea siguiente (en el primero no para, pero es necesario ponerlo...). Vemos que al poner el primer punto, nos indica un nmero de lnea. Nosotros debemos poner otro punto en la lnea cuyo nmero es el siguiente al que nos acaba de indicar. Una vez hecho esto, ya podemos ejecutar el programa (run): Podemos ir viendo los valores de los registros mediante info registers o bien con p/x
La traza paso a paso del programa la haremos con la orden step . A cada paso nos va mostrando la instruccin a ejecutar a continuacin; mediante las rdenes anteriores podremos ir viendo cmo cambia el contenido de los registros:
.data byteval: .byte 204 .text .global _start _start: movzbw byteval, %ax # %eax is now 204 movzwl %ax, %ebx # %ebx is now 204 movzbl byteval, %esi # %esi is now 204 # Linux sys_exit mov $1, %eax xorl %ebx, %ebx int $0x80
.file "MayorenArreglo.s" .section .data Tam: .long 5 Arreglo: .int 0,2,4,8,10 prueba: .long 4 .text .global _start _start: movl Tam, %ecx loop1: cmpl %eax, Arreglo(,%ecx,4) JLE else movl Arreglo(,%ecx,4), %eax jmp fin else: #En este caso no hay Else fin: loop loop1 xorl %eax, %eax incl %eax xorl %ebx, %ebx int $0x80 .end
LA SENTENCIA if-then-else
CMP: Sirve para comparar. Internamente es una resta entre los 2 operandos que se comparan JE: Este es un salto que se ejecuta cuando la comparaci n anterior dio como resultado que los 2 operandos son iguales. JNE: Salta si no es igual. JG: Salta si es mayor. JGE: Salta si es mayor o igual. JL: Salta si es menor. JLE: Salta si es menor o igual. JMP: Salta siempre. 1. cmp A,B 2. <Salto> else 3. #SECUENCIA DE PASOS SI SE CUMPLE LA CONDICIN 4. jmp fin 5. else: 6. #SECUENCIA DE PASOS SI NO SE CUMPLE LA CONDICIN 7. fin:
LA SENTENCIA WHILE
1. while: 2. cmp a,b 3. <Salto> salir 4. #SECUENCIA DE PASOS DENTRO DEL WHILE 5. jmp while 6. salir: Algoritmo de un nmero Mayor en Arreglo
FUNCIONES Y MODULARIDAD
A medida que un programa aumenta de complejidad, se detectan ciertas porciones de cdigo que realizan lo mismo basados en 0 o mas parmetros. A estos fragmentos, podemos agruparlos en funciones. Independiente de la arquitectura utilizada, una funcin est constituida por dos componentes: El Llamador y el Llamado El Llamador (Caller ) y el Llamado (Called) El Llamador1 es la parte del cdigo que tiene estos objetivos:
Colocar Los parmetros en el sitio adecuado Guardar la direccin de retorno Invocar el Llamado El Llamado, es la parte que se encarga de:
Garantizar que todo quede como estaba. Localizar (si los hay) los parmetros. Definir (si las hay) las variables locales. Recuperar la direccin de retorno Retornar el control al llamador.
1. .section .data 2. 3. a: .long 4 4. b: .long 5 5. 6. .section .text 7. .global sumar 8. sumar: 9. pushl %ebp 10. movl %esp, %ebp 11. movl 8(%ebp), %ebx 12. movl 12(%ebp), %eax 13. addl %ebx, %eax 14. #leave 15. ret
17. .global _start 18. _start: 19. # "Main" 20. pushl a 21. pushl b 22. call sumar 23. popl %ebx 24. popl %ebx 25. # Finalizo el programa 26. xorl %eax, %eax 27. incl %eax 28. xorl %ebx, %ebx 29. int $0x80
30. end
Plataforma: IA-32
Tipos de datos
La informacin se puede accesar de diversas maneras. Se puede leer un slo byte (8 bits) o un conjunto de bytes, en esta mquina en particular se denomina palabra a dos bytes y doble palabra a 4 bytes. La notacin puede ser en decimal o en hexadecimal.
Plataforma: IA-32
Tamaos de los datos:
Plataforma: IA-32
Orden de los datos en memoria:
En gas las instrucciones utilizan un sufijo para indicar el tamao de los datos sobre los cuales operan. El sistema guarda los datos en memoria en secuencia inversa de bytes (little endian) lo cual trae como consecuencia que el byte menos significativo se ubica en la posicin de menor orden y el byte ms significativo en la posicin de memoria de mayor orden. Por ejemplo si se transfiere el dato 0x457A a las posiciones consecutivas de memoria 0x100 y 0x101 se ubica el byte 7A en la posicin 0x100 y el byte 45 en la posicin 0x101.
Plataforma: IA-32
Registros de propsito general
Los registros de propsito general se utilizan para almacenar datos temporalmente, debido a que estos registros han evolucionado desde una mquina de 8 bits (el 8080) un grupo de registros an se puede acceder de 8 bits para mantener compatibilidad con toda la lnea de procesadores.
An cuando estos registros pueden mantener cualquier tipo de datos, algunos tienen cierta funcionalidad especfica o son usados de manera especial por algunas instrucciones.
Plataforma: IA-32
Registros de propsito general
Plataforma: IA-32
Registros de propsito general
En gas los registros se denotan usando el smbolo de porcentaje antes del nombre del registro. Los registros %eax, %ebx, %ecx y %edx pueden ser accesados con tamaos de 8, 16 o 32 bits cambiando su nomenclatura de acuerdo al tamao. Ejemplo para %eax:
Plataforma: IA-32
Registros de propsito general
Los registros %edi, %esi, %ebp y %esp se pueden accesar como registros de 16 o 32 bits. Ejemplo para %edi:
Plataforma: IA-32
Registro de instruccin:
El registro de instruccin o
cuales son tratados como una pila. Se nombran %st(0), %st(1), %st(2), etc. %st(0) se ubica en el tope de la pila.
Banderas:
acerca del estado actual de la mquina y el resultado de procesamiento de una instruccin. La plataforma IA-32 utiliza un registro de 32 bits llamado EFLAGS que contiene las banderas.
Plataforma: IA-32
Banderas:
Estas son las banderas ms comunes:
Plataforma: IA-32
Banderas:
La bandera de acarreo se activa cuando se produce acarreo en una operacin matemtica entre nmeros sin signo. La bandera de paridad se usa para indicar si el resultado, en un registro, de una operacin matemtica es vlido.
La bandera de ajuste se utiliza en operaciones matemticas con nmeros decimales codificados en binario (BCD). Se activa si hay acarreo presente.
La bandera de cero se activa si el resultado de una operacin es cero. La bandera de signo muestra el bit ms significativo del resultado de una operacin, el cual denota el signo del nmero.
Plataforma: IA-32
Banderas:
La bandera de direccin controla la seleccin de autoincremento o autodecremento de los registros %edi o %esi durante las operaciones con cadenas de caracteres.
La bandera de direccin slo se utiliza con las instrucciones para el manejo de cadenas de caracteres.
La bandera de desbordamiento se utiliza en la aritmtica de enteros con signo cuando un nmero sobrepasa la capacidad de representacin del registro.
Programacin en ensamblador
Un programa en lenguaje ensamblador est orientado a lneas y cada enunciado especifica una operacin sencilla. Conceptos bsicos de los programas en lenguaje ensamblador: Espacio: Un espacio es equivalente a cualquier nmero de espacios o tabuladores. Los espacios no pueden aparecer en medio de un nmero o identificador.
Comentario: Texto que aparece despus de un carcter de inicio de comentario, el ensamblador ignora los comentarios. Los comentarios comienzan con el smbolo #.
Programacin en ensamblador
Conceptos bsicos de los programas en lenguaje ensamblador: Identificador: Es una letra seguida de cualquier cantidad de letras o dgitos (y en algunos casos caracteres especiales).
Debe haber un espacio entre el nombre de la operacin y la lista de operandos. Los operandos se separan por comas, el nmero de operandos en la lista depende de la operacin.
Programacin en ensamblador
Conceptos bsicos de los programas en lenguaje ensamblador: Directriz: consiste en un nombre de directriz seguido de una lista de parmetros. Los nombres de la directrices comienzan con un punto.
Con las directrices se especifica la forma en que el ensamblador traduce las instrucciones, es decir, las directrices dirigen el proceso de traduccin.
Programacin en ensamblador
El programa escrito en lenguaje ensamblador se compone de varias secciones. Las secciones ms comunes son:
seccin de texto
seccin de datos seccin bss. En la seccin de texto se escriben las instrucciones, en la seccin de datos los datos inicializados y en la seccin bss las variables sin inicializar. Cada una de las secciones se declara por medio de una directiva.
Programacin en ensamblador
Para declarar las secciones mencionadas se usan las siguientes directivas:
.section .text para la seccin de texto .section .data para la seccin de datos .section .bss para la seccin bss Comnmente las secciones se colocan en la siguiente secuencia: .section .data .section .bss .section .text bss son las siglas correspondientes a "block storage start", que significa inicio de bloque de almacenaje.
Programacin en ensamblador
Punto de inicio de programa: se define por medio de la declaracin de una etiqueta: _start la cual indica a partir de qu instruccin se comienza a ejecutar el cdigo. Esta etiqueta debe ser declarada como global, es decir, que est disponible para aplicaciones externas; esto se logra utilizando la directiva .globl.
Programacin en ensamblador
Finalizacin del programa: Gas no provee una instruccin de fin de ejecucin, esto se logra mediante una llamada al sistema. Para realizar esta llamada se pasa dos parmetros: El valor 1 en el registro %eax indica el cdigo de llamada a exit (salida). El valor 0 en el registro %ebx indica la salida normal del programa.
Programacin en ensamblador
Estructura general En general la estructura de un programa en lenguaje ensamblador tiene la siguiente forma:
Programacin en ensamblador
Los datos se definen en las secciones .data y .bss. Para definir datos en la seccin .data se pueden utilizar las siguientes directivas:
Programacin en ensamblador
El formato para estas directivas es el siguiente:
Se pueden definir mltiples valores en la misma lnea. Cada uno de ellos ser guardado en memoria en el orden en el cual fueron declarados.
Programacin en ensamblador
Declaracin de mltiples valores con una misma etiqueta
En este caso cuando se lee la variable var arroja el valor 10, para poder leer el siguiente valor se debe incrementar la direccin de var en 4 bytes (debido a que la variable est declarada como long, es decir de 32 bits) de esta manera se usa la etiqueta var como la direccin inicial de estos valores y su tratamiento es el de un arreglo donde cada acceso se realiza tomando var como posicin inicial lo cual sera equivalente a decir var[0] y las posiciones siguientes como un desplazamiento de 4 bytes cada uno. Para leer por ejemplo el valor 30 se accesara var+8.
Programacin en ensamblador
Cuando se definen las variables, el sistema las guarda en forma consecutiva en memoria. Por ejemplo si se definen variables de 16 bits y luego se leen usando instrucciones de 32 bits el sistema no produce un mensaje de error y accesa los bytes consecutivos leyendo datos no vlidos.
An cuando la seccin .data se utiliza principalmente para definir variables tambin puede ser usada para definir constantes. Esto se hace usando la directiva .equ, el formato para esta directiva es:
directiva smbolo, valor Ejemplo: definicin de constantes
Programacin en ensamblador
Para definir datos en la seccin bss se usan dos directivas:
La directiva .lcomm se usa para datos locales, que no sern usados fuera del cdigo local. El formato, para ambas directivas es el siguiente: directiva smbolo, tamao en bytes Ejemplo : declaracin de un rea de memoria sin inicializar
Programacin en ensamblador
La ventaja de declarar variables en la seccin .bss es que esos datos no se incluyen en el programa ejecutable y por lo tanto el tamao total del programa es menor al tamao generado por la declaracin equivalente en la seccin .data.
Programacin en ensamblador
Instrucciones Las instrucciones en gas tienen un sufijo que indica el tamao del dato sobre el cual acta la instruccin.
Las instrucciones pueden no tener operandos, tener un slo operando o dos operandos; dependiendo de la instruccin en particular. En general las instrucciones tienen la forma: instruccin operando fuente, operando destino
Programacin en ensamblador
Los operandos se pueden clasificar en tres tipos:
Programacin en ensamblador
Hay varias maneras de obtener la informacin las cuales se pueden resumir en la siguiente tabla:
inm denota un inmediato reg denota un registro, regb un registro base y regi un registro ndice e es la escala la cual puede ser 1, 2, 4 8 R[reg] significa el contenido del registro reg M[x] significa el contenido de la posicin de memoria con direccin x
Programacin en ensamblador
Ejemplo: valores para cada modo de direccionamiento Asumiendo los contenidos de: %eax= 0x100 y %ecx= 0x10
Programacin en ensamblador
El valor inmediato se puede expresar en decimal o en hexadecimal como se puede observar en el siguiente ejemplo: Asumiendo el contenido de %eax=0x100
Programacin en ensamblador
La instruccin mov
La instruccin mov permite el movimiento de datos, ya que gas utiliza un prefijo para sealar el tamao de los datos podemos tener tres opciones al momento de realizar una transferencia de datos:
movb movw movl mueve un byte mueve una palabra (2 bytes) mueve dos palabras (4bytes)
Usaremos la nomenclatura F para denotar el operando fuente y D para denotar el operando destino.
Programacin en ensamblador
La instruccin mov La instruccin mov permite el movimiento de datos, ya que gas utiliza un prefijo para sealar el tamao de los datos podemos tener tres opciones al momento de realizar una transferencia de datos: movb movw movl mueve un byte mueve una palabra (2 bytes) mueve dos palabras (4bytes)
Usaremos la nomenclatura F para denotar el operando fuente y D para denotar el operando destino.
Programacin en ensamblador
Movimiento de datos inmediatos a registro o a memoria Los datos inmediatos se especifican directamente en la instruccin. Deben estar precedidos por el smbolo dlar para indicar que son datos inmediatos. Pueden estar expresados en decimal o hexadecimal. Ejemplo:
Programacin en ensamblador
Movimiento de datos entre registros Esta es la transferencia de datos que toma menor tiempo dentro del sistema es buena prctica de programacin utilizar este tipo de transferencia en vez de accesos a memoria ya que ello redunda en una mayor eficiencia. Ejemplo:
Programacin en ensamblador
Movimiento de datos entre memoria y registros Las direcciones de memoria usualmente se expresan con etiquetas, cuando por ejemplo se escribe:
a debe haber sido declarado en la seccin de datos. Como se estn transfiriendo 4 bytes stos sern guardados en memoria de manera consecutiva a partir de la posicin a.
Programacin en ensamblador
Movimiento de datos entre memoria y registros Para mover la informacin de memoria a un registro se escribe:
Programacin en ensamblador
Para leer o escribir en arreglos se utiliza direccionamiento indexado. Hay que tomar en cuenta la direccin inicial del arreglo y utilizar un ndice para ir recorriendo cada uno de sus elementos. El tamao de los datos representa el desplazamiento entre dos posiciones del arreglo. La forma general de accesar un arreglo se puede expresar como: direccin inicial (desplazamiento, ndice, escala) La direccin se calcula como: direccin inicial + desplazamiento + ndice *escala Donde escala refleja el tamao del tipo de dato del arreglo.
Programacin en ensamblador
Ejemplo de declaracin y lectura de un arreglo de enteros:
Programacin en ensamblador
Movimiento de datos con extensin Hay dos instrucciones adicionales que permiten mover datos extendindolos.
Programacin en ensamblador
Movimiento de datos con extensin Hay dos instrucciones adicionales que permiten mover datos extendindolos.
La instruccin movsbl toma un operando fuente de 1 byte, ejecuta una extensin de signo a 32 bits y lo copia al destino de 32 bits. La instruccin movzbl hace un procedimiento similar pero extiende con ceros. Ejemplo de movsbl:
Ejemplo de movzbl:
Programacin en ensamblador
Uso de la pila La pila es un rea de memoria que crece desde una direccin inicial hacia direcciones menores. El ltimo elemento colocado en la pila es el que est disponible para ser retirado. Las instrucciones para el manejo de la pila son dos, una para apilar un operando fuente y una para desapilar el valor que est en el tope de la pila y colocarlo en un operando destino.
Programacin en ensamblador
Uso de la pila
Ejemplo de uso de las instrucciones pushl y popl: Dados los valores iniciales: %esp=0x108 %ebx=0xCD %eax=0xAB %ecx=0xFE Al ejecutarse las instrucciones:
Ocurre lo siguiente:
Programacin en ensamblador
Programacin en ensamblador
Uso de la pila
Carga direccin efectiva (instruccin leal) La instruccin leal (load effective address) permite obtener la direccin de un operando en vez de su valor.
Programacin en ensamblador
Carga direccin efectiva (instruccin leal) La instruccin leal (load effective address) permite obtener la direccin de un operando en vez de su valor.
Programacin en ensamblador
Instrucciones aritmticas y lgicas Para estas instrucciones usaremos el sufijo l para los ejemplos aunque tambin se pueden usar con b o w.
Programacin en ensamblador
Instrucciones aritmticas y lgicas
Programacin en ensamblador
Operaciones aritmticas especiales
Programacin en ensamblador
Ejemplo de un programa que realiza algunas operaciones aritmticas:
Programacin en ensamblador
Control de flujo Los cdigos de condicin describen los atributos de las operaciones aritmticas y lgicas ms recientes. Por lo tanto la ejecucin de las instrucciones aritmticas y lgicas afectan las banderas. La instruccin leal no afecta las banderas. Las banderas ms comunes son las siguientes: Bandera de Acarreo (CF), bandera de Cero (ZF), bandera de Signo (SF) y bandera de desbordamiento (OF). Las instrucciones cmpl y testl actualizan las banderas sin afectar los operandos. Tambin existen las versiones con los sufijos b y w.
Programacin en ensamblador
Control de flujo
testl generalmente se usa repitiendo el operando para saber si es cero, positivo o negativo. Ejemplo:
Programacin en ensamblador
Actualizacin de un registro con valor de una bandera Es posible transferir el contenido de una bandera o una condicin que dependa de la combinacin de varias banderas a un registro. Esto es til para poder usar la misma condicin en varias partes del programa. Estas instrucciones slo mueven un byte, las ms usadas son:
Programacin en ensamblador
Actualizacin de un registro con valor de una bandera Ejemplo:
Programacin en ensamblador
Las instrucciones de salto Una instruccin de salto produce un cambio en la ejecucin del programa pasando a una nueva posicin, no secuencial. La nueva direccin se representa con una etiqueta.
Programacin en ensamblador
Las instrucciones de salto La instruccin jmp salta de manera incondicional, es decir, no chequea ningn cdigo de condicin. El salto puede ser directo a la direccin representada por la etiqueta o indirecto donde la direccin de destino se lee de un registro o de una posicin de memoria. Ejemplo de salto incondicional directo:
En este caso la tercera instruccin no se ejecuta ya que al ejecutarse el salto el programa pasa a ejecutar la instruccin 4.
Programacin en ensamblador
Las instrucciones de salto Ejemplo de salto incondicional indirecto: Dados los siguientes valores: %eax = 0x120 y el contenido de la posicin de memoria 0x120 = 0x180
Programacin en ensamblador
Las instrucciones de salto Las otras instrucciones de salto son condicionales lo cual significa que la mquina revisa los cdigos de condicin antes de realizar el salto, si la condicin se cumple realiza el salto a la etiqueta especificada, si la condicin no se cumple contina la ejecucin de manera secuencial. Ejemplo de salto condicional:
Programacin en ensamblador
Las instrucciones de salto Podemos observar en el ejemplo anterior que si se cumple la condicin de comparacin valor1<valor2 el programa salta a la instruccin 7 sin pasar por las instrucciones 5 y 6. En caso de no cumplirse la condicin, es decir valor1>=valor2, entonces contina la ejecucin en la instruccin siguiente, en este caso, la instruccin 5. Es importante destacar la necesidad de introducir la instruccin 6, un salto incondicional a etiq2 ya que de no estar presente la mquina seguira el orden secuencial y luego de realizar la resta ejecutara la suma lo cual arrojara un resultado errneo. En este programa slo se ejecuta una de las dos operaciones aritmticas, la resta en caso de cumplirse la condicin o la suma en caso de que no se cumpla.
Programacin en ensamblador
Traduccin de estructuras condicionales Para traducir expresiones de la forma: if (condicin) instrucciones else instrucciones En lenguaje ensamblador se utilizan los saltos condicionales. Por ejemplo para realizar un extracto de programa que haga lo siguiente: if (x>y) result=x-y; else result=y-x;
Programacin en ensamblador
Programacin en ensamblador
Ciclos
En lenguaje ensamblador se implementan los ciclos usando comparaciones de condiciones y saltos.
Do while
While
For
Do while
Consideremos el siguiente programa en lenguaje C para encontrar el valor del elemento n en la serie de Fibonacci. La serie se define como F1=1; F2=1; Fn=Fn-1+Fn-2 para n>=3. Por ejemplo los primeros 7 elementos de la serie son: 1,1,2,3,5,8,13 int fib_dw(int n) { int i=0; int val=0; int nval=1; int t=0; do { t=val+nval; val=nval; nval=t; i++; } while (i<n); return val; }
Do while Asumamos que el parmetro n est en el registro %esi. Usaremos los registros %ecx, %ebx, %edx y %eax para representar las variables i, val, nval y t respectivamente.
Do while
While
Cuando se utiliza while se comprueba la condicin antes de entrar en el ciclo. Se puede reescribir el ejemplo anterior utilizando un while de la siguiente manera: int fib_w(int n) { int i=1; int val=1; int nval=1;
int t=0;
while (i<n) { t=val+nval; val=nval; nval=t; i++; } return val; }
While De nuevo asumimos que n est en %esi y usamos los mismos registros anteriores:
While
For
En este caso tambin se revisa la condicin antes de entrar al ciclo. El ejemplo anterior escrito con un for:
int fib_f(int n) { int i; int val=1; int nval=1; int t=0; for(i=1; i<n; i++) { t=val+nval; val=nval; nval=t; } return val; }
For
De nuevo usamos las mismas variables:
Arreglos
Cuando se declara un arreglo en C se escribe una expresin con la siguiente estructura: T A[N] Esta declaracin designa el nombre del arreglo como A de
Arreglos
Ejemplo de declaracin y parmetros de arreglos en C: Al declarar los arreglos: char A[12]; int B[8]; double C[6]; Sus parmetros sern los siguientes:
Arreglos
En lenguaje ensamblador se utiliza el tipo de direccionamiento escalado para acceder a las posiciones de un arreglo. Por ejemplo si se declara el arreglo a como:
Los elementos del arreglo se guardan en memoria en posiciones consecutivas y el identificador a apunta a la primera posicin del arreglo, es decir la posicin cuyo ndice es cero.
Arreglos
Para leer un elemento de a se puede utilizar la siguiente instruccin:
donde a es la direccin inicial del arreglo y se utiliza un registro, en este caso %ebx como ndice. La escala (4) viene dada por el tamao del tipo de dato del arreglo, en este caso es 4 porque a est declarado como "long".
Arreglos
Ejemplo: dado un arreglo de 10 posiciones, inicializado, sumar los valores del arreglo y guardar el resultado en la variable "result"
Arreglos
Cuando se declara un arreglo de dos dimensiones como por ejemplo el arreglo A declarado en C como: int A[4][3]; Se reserva el espacio de memoria para 12 elementos (4*3) y se ordenan de la siguiente manera:
En general para un arreglo de dos dimensiones A[R][C] el elemento A[i][j] est en la direccin XA + nmero de bytes del tipo de dato por (C * i + j). Para el ejemplo anterior el elemento A[2][2] est en la direccin XA+4(3*2+2)=XA+32.
Arreglos
Ejemplo en ensamblador: Declaracin del arreglo a de dos dimensiones a [3][2] con los siguientes contenidos:
Arreglos
Los primeros procesadores Intel no tenan soporte de hardware para las operaciones de punto flotante. Esto no significa que ellos no podan efectuar operaciones de punto flotante. Esto slo significa que ellas se realizaban por procedimientos compuestos de muchas instrucciones que no son de punto flotante. Para estos primeros sistemas, Intel suministraba un circuito integrado adicional llamado coprocesador matemtico. Un coprocesador matemtico tiene instrucciones de mquina que realizan instrucciones de punto flotante mucho ms rpido que usando procedimientos de software (en los primeros procesadores se realizaban al menos 10 veces ms rpido). 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 el coprocesador matemtico en el 80486 en s mismo. Desde el Pentium, todas las generaciones del 80X86 tienen un coprocesador matemtico interno; sin embargo todava se programa como si fuera una unidad separada. An los primeros sistemas sin un coprocesador pueden instalar un software que emula el coprocesador matemtico. Estos emuladores se activan automticamente cuando un programa ejecuta una instruccin del coprocesador y corre un procedimiento que produce los mismos resultados que el coprocesador (mucho ms lento claro est. El coprocesador numrico tiene ocho registros de punto flotante. Cada registro almacena 80 bits. Los nmeros de punto flotante se almacenan en estos registros siempre como nmeros de 80 bits de precisin extendida. Los registros se llaman STO, ST1, . . . ST7. Los registros de punto flotante se usan diferentes que los registros enteros en la CPU. Los registros de punto flotante estn organizados como una pila. Recuerde que una pila es una lista LIFO (Last In Firist Out). STO siempre se refiere al valoren el tope de la pila. Todos los nmeros nuevos se aaden al tope de la pila. Los nmeros existentes se empujan en la pila para dejarle espacio al nuevo nmero. Hay tambin un registro de estado en el coprocesador numrico. Tiene varias banderas. Slo las 4 banderas usadas en comparaciones sern estudiadas. C0 , C1 , C2 and C3 . El uso de ellas
Conversin a binario del nmero decimal 0.5625 Conversin a binario del nmero decimal 0.85
El IEEE (Institute of Electerical and Electronic Engineer) es una organizacin internacional que ha diseado formatos binarios especficos para almacenar nmeros de punto flotante. Este formato se usa en la mayora de (pero no en todas) las computadoras hechas hoy da. A menudo es soportado por el hardware de la computadora en s mismo. Por ejemplo el coprocesador numrico (o matemtico) de Intel (que est insertado en todas las CPU que hacen uso de este procesador desde el Pentium) lo usa. El IEEE define dos formatos con precisin diferentes: precisin simple y doble, la precisin simple es usada por las variables del tipo float en C y la precisin doble es usada por la variable double.
El coprocesador matemtico de Intel utiliza un tercer nivel de mayor precisin llamado precisin extendida. De hecho, todos los datos en el coprocesador en s mismos estn en esta precisin. Cuando se almacenan en la memoria desde el coprocesador se convierte a precisin simple o doble automticamente. La precisin extendida usa un formato general ligeramente
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, ste uno no se almacena. Esto permite el almacenamiento de un bit adicional al final y as se incrementa un poco la precisin. Esta idea se conoce como la representacin oculta del uno.
Cmo se podra 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 fraccin es (recuerde el uno de adelante est oculto). Colocando todo esto unido (para ayudar a aclarar las diferentes secciones del formato del punto flotante, el bit de signo y la fraccin han sido subrayados y los bits se han agrupado en nibles):
La doble precisin IEEE usa 64 bits para representar nmeros y normalmente son exactos los 15 primeros dgitos decimales significativos. Como muestra la Figura 6.4, el formato bsico es muy similar a la precisin simple. Se usan ms bits para el exponente (ll) y la fraccin (52) que para la precisin 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 precisin simple). Segundo, se permite un gran rango de exponentes verdaderos (y as usa un gran rango de magnitudes). Las magnitudes de precisin doble van de 10308 hasta 10308 aproximadamente. En el campo de la fraccin el responsable del incremento en el nmero dgitos significativos para los valores dobles. Como un ejemplo, considere nuevamente 23.85 otra vez. El exponente polarizado ser 4 + 3FF = 403 en hexadecimal. As la representacin doble sera: 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 aproximacin mucho mejor de 23.85. La precisin doble tiene los mismos valores especiales que la precisin simple. 3 Los nmeros no normalizados son muy similares tambin. La principal diferencia es que los nmeros dobles sin normalizados usan 21023 en lugar de 2127 .
Estos dos nmeros no tienen el mismo exponente as que se desplaza la mantisa para hacer iguales los exponentes y entonces sumar:
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,000110023 (o 1,0000110024 ) es igual a 10000,1102 o 16.75. Esto no es igual a la respuesta exacta (16.71875) Es s lo una aproximacin debido al error del redondeo del proceso de la suma. Es importante tener en cuenta que la aritmtica de punto flotante en un computador (o calculadora) es siempre una aproximacin. Las leyes de las matemticas no siempre funcionan con nmeros de punto flotante en un computador. Las matemticas asumen una precisin infinita que un computador no puede alcanzar. Por ejemplo, las matemticas ensean que (a+b)b = a; sin embargo, esto puede ser exactamente cierto en un computador.
La resta trabaja muy similar y tiene los mismos problemas que la suma. Como un ejemplo considere 16,75 15,9375 = 0,8125: 1,0000110 24 1,1111111 23
Los primeros procesadores Intel no tenan soporte de hardware para las operaciones de punto flotante. Esto no significa que ellos no podan efectuar operaciones de punto flotante. Esto slo significa que ellas se realizaban por procedimientos compuestos de muchas instrucciones que no son de punto flotante. Para estos primeros sistemas, Intel suministraba un circuito integrado adicional llamado coprocesador matemtico. Un coprocesador matemtico tiene instrucciones de mquina que realizan instrucciones de punto flotante mucho ms rpido que usando procedimientos de software (en los primeros procesadores se realizaban al menos 10 veces ms rpido). 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 el coprocesador matemtico en el 80486 en s mismo 5 . Desde el Pentium, todas las generaciones del 80X86 tienen un coprocesador matemtico empotrado; sin embargo todava se programa como si fuera una unidad separada. An los primeros sistemas sin un coprocesador pueden instalar un software que emula el coprocesador matemtico. Estos emuladores se activan automticamente cuando un programa ejecuta una instruccin del coprocesador y corre un procedimiento que produce los mismos resultados que el coprocesador (mucho ms lento claro est).
section .data valor: dd 0 ;argc suma: dd 0 ; sumatoria de los enteros formato: db '%.3f', 10, 0 ;Formato de impresin de resultado error: db 'No se introducieron valores', 10, 0 ;mal forma de uso global main extern printf extern atoi section .text main: mov ecx, [esp+4] ; dec ecx ; Checar que se hayan introducido enteros. jz nada mov [valor], ecx ; guardar el numero introducido argc o argumentos de enteros. mov edx, [esp+8] ; agregar: push ecx ; insertar dato en la pila push edx ; insertar dato en la pila push dword [edx+ecx*4] ; argv[ecx] call atoi ; convertir el arg de entrada a entero add esp, 4 ; aumentar el apuntador de la pila pop edx ; sacar registros usados de los registros pop ecx add [suma], eax ; guardar dato en sumatoria dec ecx jnz agregar ; si hay mas argumentos continua el ciclo.
average: fild dword [suma] fild dword [valor] fdivp st1, st0 sub esp, 8 promedio (resultado) fstp qword [esp] push formato call printf add esp, 12 ret nada: push error call printf add esp, 4 ret
; cargar en st0 la sumatoria de enteros en la pila ; cargar en st0 numero de enteros (argc) en la pila ; promedio = (suma / valor) st1 / st0 ; mover el apuntador para posicinarlo en el ; almacena el promedio y saca de la pila ; alistar de impresion ; llamar a la libreria de C para imprimir ; avanzar el apuntador para sacar los resultados
; mensaje de error
Para la multiplicacin, 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, el resultado real podra ser redondeado a 8 bits para dar: La divisin es ms complicada, pero tiene problemas similares con errores de redondeo. El principal punto de esta seccin es que los clculos de punto flotante no son exactos. El programador necesita tener cuidado con esto. Un error comn que el programador hace con nmeros de punto flotante es compararlos asumiendo que el clculo es exacto. Por ejemplo considere una funcin llamada f (x) que hace un clculo complejo y un programa est tratando de encontrar las races de la funcin. 4 . Uno podra intentar usar la siguiente instruccin para mirar si x es una raz: if ( f (x) == 0.0 )