Aparentes Contradicciones en Programacion C - 5
Aparentes Contradicciones en Programacion C - 5
Aparentes Contradicciones en Programacion C - 5
UADY
Abstract
This article shows in a practical way, using four programs as examples, how crucial is the computer architecture knowledge from programmer to understand how high-level programming languages due to its compilers programs interact directly with the hardware. En este artculo se muestra en forma prctica, utilizando cuatro programas como ejemplo, como es que el conocimiento, por parte del programador, de la arquitectura de la computadora es determinante para comprender el funcionamiento de los lenguajes de programacin de alto nivel ya que los compiladores de estos programas interactan directamente con el hardware.
1. Antecedentes
En los primeros das de la computacin, cuando von Newman propuso su revolucionario concepto de programa almacenado, el lenguaje de programacin que exclusivamente se utilizaba era el lenguaje mquina. Dada sus mltiples desventajas se procedi a desarrollar el lenguaje ensamblador que es una transcripcin nemnica de aquel, adems de tener otras prestaciones, liberando al programador del tedio excesivo que supona programar en bajo nivel, interactuando con los bits, puertos, direcciones y registros en forma directa. Con la misma idea se desarrollaron posteriormente los lenguajes de alto nivel que aslan completamente al programador de la arquitectura subyacente de la mquina. Actualmente es ms frecuente que los profesionales de la computacin interacten con el hardware sin perder las ventajas que supone utilizar un lenguaje de alto nivel. Para poder interactuar con el hardware se desarroll el lenguaje de nivel medio: El lenguaje de programacin C, que permite acceder al hardware y tener las prestaciones de los lenguajes de alto nivel. Para utilizar el lenguaje de programacin C en forma eciente y ecaz se requiere del conocimiento de la arquitectura de la computadora donde se ejecutarn los programas. El objetivo de este artculo es explicar porqu un desconocimiento del hardware puede producir, en los resultados esperados, algunas aparentes contradicciones que se irn aclarando en este artculo. El tamao en bits de los nmeros enteros no est normalizado por ninguna asociacin, generalmente es funcin de la arquitectura de la computadora donde se ejecutar el programa. Por ejemplo, hoy da, los
2010 IEEE Computer Society Subject Classif ication. D,3,0 [Sof tware] [P rogramming Languages][General] Keywords and phrases : Bajo nivel, alto nivel, arquitectura, lenguaje C.
Resumen
microcontroladores de ocho bits usan enteros de ese tamao[1]; as el rango de nmero sin signo que soportan es de 0 a 255 (000000002 a
cuando la PC tena registros internos de 16 bits los nmeros enteros en el lenguaje C para esta arquitectura
00000000000000002 al 11111111111111112 , es decir de 0 al 65, 53510 y para los enteros con signo el rango va de 10000000000000002 a1 01111111111111112 , es decir de -32,768 al 32,767. Se puede observar una asimetra entre los nmeros
se deniern con ese tamao, de este modo el rango de valores enteros sin signo va de negativos y positivos; esto se debe a la representacin de complemento a dos que se utiliza para representar nmeros negativos. A partir del surgimiento del microprocesador de 32 bits [2], los compiladores de C tomaron como tamao para los nmeros enteros los 32 bits, as el rango de nmeros enteros sin signo en C para esta arquitectura es de
0000000016
al
va de8000000016 a1
F F F F F F F F16 es decir de 0 al 4, 294, 967, 29510 y para los 7F F F F F F F16 es decir de -2,147,483,648 al 2,147,483,647.
byte del entero % d\n " , sizeof ( int ));
podemos determinar el tamao en bytes que utiliza nuestro lenguaje C para representar los nmeros enteros con signo. En conclusin los nmeros enteros en el lenguaje C estn fuertemente relacionados con el tamao de los registros internos de datos de la arquitectura para la cual fue diseado el compilador. Por otra parte el tamao en bits para los nmeros en punto otante se encuentra normalizado por la IEEE 754 [4] que dene tres tipos de tamaos en bits para dichos nmeros, los cuales son: Precisin sencilla (32 bits), Doble precisin (64 bits) y Precisin extendida (80 bits). El formato de 32 bits comienzan con un bit de signo para el nmero en su totalidad, el bit ms signicativo; el exponente con signo est conformado por los siguientes ocho bits el cual segn la norma IEEE 754 se representa en exceso a 127, los exponentes 0 y 255 no se usan como nmeros normalizados, de esto solo son vlidos los exponentes 1 a 254 con rango de 1 a 126 para exponentes negativos y de 128 a 254 para exponentes positivos. Por ltimo los restantes 23 bits llamada signicante o mantisa, ver Cuadro 1. 31 Bit de signo 30 .......................................... 23 Exponente exceso a 127 22 ........................................ 0 Mantisa con un uno y punto decimal mplicito Cuadro 1: Representacin de nmeros en punto otante de la norma IEEE754
La mantisa consiste en un bit 1 implcito, un punto binario implcito y 23 bits arbitrarios, en estricta forma la mantisa consta de 24 bits y el punto decimal. Si todos los bits de la mantisa son cero entonces la mantisa tiene el valor numrico 1.0 en base diez (1,0000000000000000000000002 ); si todos los bits de la mantisa son unos entonces la mantisa tiene un poco menos del valor numrico 2.0 en base diez (1,1111111111111111111111112 ). Todos los nmeros normalizados (que usa el lenguaje de programacin C) tienen un signicado en su mantisa, s dentro del intervalo 1
s<2
en base diez.
2.
Casos de estudio
Ahora exponemos los casos de estudio donde el lenguaje de programacin C tiene un comportamiento
que parece extrao al usuario del lenguaje. Estos casos fueron seleccionados en virtud de que los alumnos de Arquitectura de Computadoras de la Facultad de Matemticas frecuentemente hacen este tipo de preguntas del por que funciona as el lenguaje C en estos casos.
programa 1
1;
( " En nmero ( " En nmero entero entero con sin signo signo es es % d\n " , % u\n " , x); x);
1.- Programa 1
En este ejemplo podemos ver que se declara e inicializa una variable local entera x con el valor -1, enseguida encontramos la funcin de librera
printf
luego sin signo. La mayora de las personas que han llevado algn curso del lenguaje de programacin C aseguran que se imprimirn los valores de -1 y 1. Sin embargo, cuando ejecutan el programa se llevan una sorpresa ya que se imprime lo siguiente: En nmero entero con signo es -1, en nmero entero sin signo es 4,294,967,295 Tras el resultado quedan sorprendidos y no saben la razn del porqu se imprime semejante nmero. Aclaremos el misterio, primero debemos recordar que las actuales PC y compiladores utilizan enteros de 32 bits; adems los nmeros negativos utilizan la representacin de complemento a dos [5], es decir cuando el software determina que se trata de un nmero negativo toma la magnitud del nmero y lo convierte en complemento a dos, es as como en la memoria el nmero -1 se almacena en 32 bit como Cuando la funcin de librera
printf
F F F F F F F F 16 .
1, es decir es un nmero negativo, automticamente imprime en pantalla el carcter luego toma el nmero le saca complemento a dos. Luego lo convierte a decimal e imprime el valor correspondiente, de este modo se imprime -1. Ahora bien cuando la funcin
printf
toma el nmero binario que se encuentra en su registro, lo convierte en decimal y lo imprime en pantalla; de ese modo imprime el nmero 4,294,967,295 base 10 que equivalente a F F F F F F F F16 . 7 6 5 4 3 2 1 0 (F x16 + F x16 + F x16 + F x16 + F x16 + F x16 + F x16 + F x16 = 4, 294, 967, 295 con
F16 = 1510 )
16777216;
y; y = &x ; // produce entero una con es advertencia signo es es el compilador x);
hexadecimal flotante
% x\n " ,
% f \n " ,
*y ) ;
2.- Programa 2
Cuando se compila el programa nos muestra la siguiente advertencia: assignment from incompatible pointer type esto se debe a que se le asign a un puntero declarado como otante un puntero de tipo entero, las direcciones en las PC de 32 bits son precisamente de esta magnitud o sea 32 bits y por lo tanto no hay problema para inicializar el apuntador de otante a entero ya que ambos apuntadores son nmeros enteros sin signo de 32 bits.
Si le preguntamos a las personas que programan en el lenguaje C acerca de las cantidades numricas que se imprimirn, la mayora responder que en el primer
printf
printf
algunas pocas dirn que se imprime FF000000H. La razn se explic en la primera parte del artculo
(es el complemento a dos de -16777216), pero ninguna acertar a decir la cantidad que se imprime en el ltimo
printf.
170141183460469231731687303715884105728
printf
imprime:
esta cantidad es muy fcil de deducir, sabemos que el nmero en punto otante de simple precisin es de 32 bits, que el bit ms signicativo representa el signo. As de su representacin en hexadecimal vemos que es un nmero negativo ya que el bit mas signicativo es 1, los siguientes ocho bits indican el exponente en exceso a 127, como se puede observar de su representacin en hexadecimal el nmero binario que representa el exponente es exponente es
111111102
254.
De este modo el
23
de acuerdo a la norma IEEE 754 la mantisa tiene el valor de 1 en base 10 y el nmero representado es (1)x2127 = 170141183460469231731687303715884105728 lo que concuerda con la cantidad impresa en el
printf
3.- Programa 3
. LC0 : . string . text . globl main : 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 pushl movl andl subl movl movl movl movl sarl idivl movl fildl fstps flds movl fstpl movl call 4( % % ebp % esp , $ 16 , $48 , $5 , $2 , 44( % $31 , 40( % 28( % 36( % % ebp % esp % esp 44( % 40( % esp ) esp ) % eax main "En n \ 3 0 3 \ 2 7 2 mero flotante es % f \n"
% eax , esp )
% edx
esp )
36( % esp )
esp ) % eax
$ . LC0 ,
% eax , ( % e s p ) printf
19 20 21
$0 ,
% eax
Listado
Programa
equivalente
en
ensamblador
al
programa
Este programa a simple vista da la impresin que imprimir 2.5, sin embargo cuando se compila y ejecuta en la PC imprime 2.0 lo cual parece un misterio. Expliquemos la razn del comportamiento de lenguaje C en esta aplicacin tpica. Para poder explicar este comportamiento obtengamos el cdigo de lenguaje ensamblador equivalente, el cual se muestra en el listado 1: Primeramente debemos saber que el lenguaje C slo tiene un operador para la divisin tanto entera como otante y que el compilador cuando divide dos enteros efecta la divisin entera a travs del CPU. Esto se puede ver analizando el programa en leguaje ensamblador equivalente al del leguaje C. Para comprender las sintaxis del lenguaje ensambrador revise la bibliografa [6], en las lneas 5 -movl $5, 44( %esp)- y 6 -movl $2, 40( %esp)- se cargan los valores 5 y 2 en la pila (variable locales), en la lnea 7 -movl 4( %esp), %eax- se mueve el valor 5 al registro interno EAX (acumulador), en la lnea 10 -idivl 40( %esp)- se efecta la divisin entera con signo entre el contenido del acumulador EAX y el contenido de la posicin de memoria 40( %esp) que contiene al operando 2, el cociente de la divisin entrega en el acumulador EAX y el resto o residuo lo entrega en el registro EDX; en la lnea 11 -movl %eax, 28( %esp)- se guarda el cociente en la pila; en la lnea 12 -ldl 28( %esp) - se convierte el entero en otante y se almacena en la pila del FPU; en la lnea 13 fstps -36( %esp)- toma el valor entero del cociente ya convertido a otante y lo almacena en la variable local x y nalmente en las lneas 14 a la 17 se cargan los parmetros en la pila para llamar la funcin
printf.
Por ltimo analicemos el programa que da la respuesta esperada del programa 3, el cual es el siguiente. #i n c l u d e < s t d i o . h> int { int x =5 , float printf return } y =2; z; x/y ; flotante es % f \n " , z ); ( " En nmero 0; main ( )
z= ( f l o a t )
4.- Programa 4
Para poder entender como trabaja el lenguaje C procedemos a obtener el cdigo de lenguaje ensamblador equivalente, muestra en el listado 2 . LC0 : . string . text . globl . type main : 1 2 3 4 5 6 7 8 9 10 11 12 13 14 pushl movl andl subl movl movl fildl fildl fdivrp fstps flds movl fstpl movl % ebp % esp , $ 16 , $32 , $5 , $2 , % ebp % esp % esp 28( % 24( % esp ) esp ) main main , @function "En n \ 3 0 3 \ 2 7 2 mero flotante es % f \n"
$ . LC0 ,
esp )
% eax , ( % e s p )
15 16 17 18 Listado
printf $0 , % eax
Programa
equivalente
en
ensamblador
al
programa
Como se puede observar, tambin se guardan los datos en formato de nmero entero en la pila : lneas 5 movl $5, 28( %esp) - y 6 -movl $2, 24( %esp)- pero a diferencia del cdigo anterior primero los convierte a otante utilizando al FPU, esto se observa en las lneas 7 -ldl 28( %esp) - y 8 -ldl 24( %esp) -, en la lnea 9 -fstps 20( %esp) - se hace la divisin otante, en la lnea 10 fstps 20( %esp) se almacena el resultado en la variable x, es por esto que el resultado es 2.5.
3.
Conclusiones.
La arquitectura de computadoras es un rea de la computacin estable en cuanto a las estructuras
conceptuales utilizadas, no as la velocidad y capacidad del hardware que ha crecido a pasos exponenciales. La fabricacin del hardware tambin ha reducido su costo. Estos cambios se ven reejados en cuanto al diseo de los nuevos compiladores que tratan de obtener las mayores prestaciones posibles de la capacidad instalada del hardware, como por ejemplo el tamao en bits de los nmeros enteros. Estos cambios obligan a los profesionales de la computacin a conocer la arquitectura de la computadora en la cual programan para poder explotar toda la potencia del lenguaje y de este modo comprender el funcionamiento de los compiladores y entender el funcionamiento de los lenguajes de alto nivel.
Referencias
1.- Custom Computer Service, Pic C compiler julio 203, pgina 66 2.- Intel, Intel 64 and IA-32 Architectures Software Developer's Manual Volumen 1, pgina 41 3.- Byron Gottfried, Programacin C, Mc Graw, Hill, segunda edicin 1997, pgina 66 4.- Willim Stallings, Organizacin y Arquitectura de Computadoras, Cuarta edicin 1997pgina 296 5.- Je Duntemann, Assembly Languaje Step by Step, Ed. Willy, tercera edicin, 2009, pgina 221 6.- Paul A. Carter, PC Assembly Languaje, January 15, 2002