Anda di halaman 1dari 6

Abstraction & Application 5 (2011) 1-6

UADY

Aparentes contradicciones en programacin C


Fernando Cob, Ral Duarte, Otilio Santos*

Facultad de Matemticas de la Universidad Autnoma de Yucatn

*fcoba@uady.mx, rduarte@uady.mx, saguilar@uady.mx

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

111111112 ) y con signo es de -128 al 127(100000002 al 011111112 ). En un principio

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 ));

enteros con signo el rango

Con la siguiente instruccin del lenguaje de programacin C p r i n t f ( " Tamao e n

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.

Primer caso de estudio


Ahora ya estamos en posibilidad de comprender como funciona el leguaje de programacin en varias aplicaciones, como primer caso de estudio veamos el 32 bits.

programa 1

escrito en lenguaje C para arquitectura de

#i n c l u d e < s t d i o . h> int { int x= main ( )

1;
( " En nmero ( " En nmero entero entero con sin signo signo es es % d\n " , % u\n " , x); x);

printf printf return } 0;

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

donde se imprime la variable x, primero con signo y

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 .

imprime el nmero con magnitud y signo; identica que el ltimo bit es

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

se le indica que imprima el nmero sin signo entonces

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 )

Segundo caso de estudio


Ahora analicemos otra situacin para la cual consideremos el programa 2 elaborado en C: #i n c l u d e < s t d i o . h> int { int x= float main ( )

16777216;
y; y = &x ; // produce entero una con es advertencia signo es es el compilador x);

printf printf printf return }

( " En nmero ( " En nmero ( " En nmero 0;

% d\n " , 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

imprime la cantidad -16777216, en el segundo

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.

Cuando el programa se ejecuta podemos ver que el ltimo

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

254 127 = +127,

111111102

que es en base diez es igual a

254.

De este modo el

la mantisa esta compuesta por

23

cero y como hay un uno implcito,

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

Tercer caso de estudio


Veamos ahora el comportamiento del siguiente programa 3 escrito en lenguaje C para PC de 32 bits #i n c l u d e < s t d i o . h> int { int x =5 , y =2; z; z=x / y ; printf return } 0; ( " En nmero flotante es % f \n " , z ); float main ( )

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"

esp ) , % edx 28( %

% eax , esp )

% edx

% eax , esp ) esp )

esp )

36( % esp )

esp ) % eax

$ . LC0 ,

% eax , ( % e s p ) printf

19 20 21

movl leave ret 1.

$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"

28( % 24( % % st , 20( % 20( % 4( %

esp ) esp ) % st (1) esp ) esp ) % eax

$ . LC0 ,

esp )

% eax , ( % e s p )

15 16 17 18 Listado

call movl leave ret 2.

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

Anda mungkin juga menyukai