2010-03-08 13 views
10

consideran este programa¿Cómo se maneja la conversión de float/double a int en printf?

int main() 
{ 
     float f = 11.22; 
     double d = 44.55; 
     int i,j; 

     i = f;   //cast float to int 
     j = d;   //cast double to int 

     printf("i = %d, j = %d, f = %d, d = %d", i,j,f,d); 
     //This prints the following: 
     // i = 11, j = 44, f = -536870912, d = 1076261027 

     return 0; 
} 

Puede alguien explicar por qué la colada de doble/float a int funciona correctamente en el primer caso, y no funciona cuando se hace en printf?
Este programa fue compilado en gcc-4.1.2 en la máquina de Linux de 32 bits.


EDIT: Zach's answer parece uso lógico, es decir, de los especificadores de formato de averiguar lo que sobresalen de la pila. Sin embargo, considere esta pregunta de seguimiento:

int main() 
{ 

    char c = 'd'; // sizeof c is 1, however sizeof character literal 
        // 'd' is equal to sizeof(int) in ANSI C 

    printf("lit = %c, lit = %d , c = %c, c = %d", 'd', 'd', c, c); 
    //this prints: lit = d, lit = 100 , c = d, c = 100 
    //how does printf here pop off the right number of bytes even when 
    //the size represented by format specifiers doesn't actually match 
    //the size of the passed arguments(char(1 byte) & char_literal(4 bytes))  

return 0; 
} 

¿Cómo funciona esto?

+0

Tuve una duda similar. Ver este hilo: http://stackoverflow.com/questions/2377733/how-does-this-program-work – Lazer

+1

char es un carácter único, es simplemente un entero de 8 bits. Cuando realiza cualquier tipo de operación en tipo integral que sea más pequeño que int, se promociona a entero. Esto incluye cuando se llama a una función. Así que, en realidad, no es una casualidad lo que hace que su llamada printf funcione, este comportamiento está definido. Hablando en términos prácticos, en la mayoría de las C ABI siempre asigna al menos una palabra de máquina por variable pasada en la pila. –

+0

@SurajJain Sí, '' El parámetro que pasa solo considera los tipos promocionados según las promociones de argumento predeterminadas (C11 §6.5.2.2/6, §7.16.1.1/2), lo que garantiza que 'char' y' int' sean compatibles . Sin embargo, esto no es exactamente lo mismo que las promociones usadas para la aritmética. Además, es peligroso razonar sobre el lenguaje en términos de ABI.Confirmar que está bien realmente requiere verificar las reglas. – Potatoswatter

Respuesta

19

La función printf utiliza los especificadores de formato para averiguar qué expulsar de la pila. Entonces, cuando ve %d, aparece en 4 bytes y los interpreta como int, lo cual es incorrecto (la representación binaria de (float)3.0 no es lo mismo que (int)3).

Deberá usar los especificadores de formato %f o enviar los argumentos al int. Si está utilizando una nueva versión bastante de gcc, después de encender advertencias más fuertes capturas de esta especie de error:

$ gcc -Wall -Werror test.c 
cc1: warnings being treated as errors 
test.c: In function ‘main’: 
test.c:10: error: implicit declaration of function ‘printf’ 
test.c:10: error: incompatible implicit declaration of built-in function ‘printf’ 
test.c:10: error: format ‘%d’ expects type ‘int’, but argument 4 has type ‘double’ 
test.c:10: error: format ‘%d’ expects type ‘int’, but argument 5 has type ‘double’ 

Respuesta a la parte editada de la pregunta: ¿

reglas de la promoción número entero de C decir que todas las tipos más pequeños que int obtener promocionado a int cuando se pasa como un vararg. Entonces, en su caso, el 'd' se promociona a un int, luego printf está saliendo de un int y fundido a un char. La mejor referencia que pude encontrar para este comportamiento fue this blog entry.

+3

+1 Me encantan las advertencias más fuertes. –

+0

Tengo una pregunta de seguimiento, vea la parte editada de mi pregunta. Comente si la pregunta no está clara ... gracias – Sandip

+1

Es cierto que los argumentos 'char' se promueven a' int' cuando se pasan en la parte variable de la lista de argumentos (esto es lo que sucede cuando 'c' se pasa en el ejemplo), pero las constantes literales de caracteres (como ''d'') ya son de tipo 'int'. (Tenga en cuenta también que, en teoría, bajo algunos compiladores, los argumentos 'char' podrían promoverse a' unsigned int' en su lugar). – caf

2

Debido a que usted no está utilizando el especificador de formato flotante, tratar con:

printf("i = %d, j = %d, f = %f, d = %f", i,j,f,d); 

De lo contrario, si quieres 4 enteros hay que echarlos antes de pasar el argumento de printf:

printf("i = %d, j = %d, f = %d, d = %d", i,j,(int)f,(int)d); 
0

printf utiliza listas de argumentos de longitud variable, lo que significa que debe proporcionar la información de tipo. Estás proporcionando la información incorrecta, por lo que se confunde. Jack proporciona la solución práctica.

4

Jack's answer explica cómo solucionar su problema. Voy a explicar por qué estás obteniendo tus resultados inesperados. Su código es equivalente a:

float f = 11.22; 
double d = 44.55; 
int i,j,k,l; 

i = (int) f; 
j = (int) d; 
k = *(int *) &f;   //cast float to int 
l = *(int *) &d;   //cast double to int 

printf("i = %d, j = %d, f = %d, d = %d", i,j,k,l); 

La razón es que f y d se pasan a printf como valores, y luego estos valores se interpretan como int s. Esto no cambia el valor binario, por lo que el número que se muestra es la representación binaria de float o double. El modelo real de float a int es mucho más complejo en el ensamblaje generado.

+2

Puede ayudarlo a entender su resultado, pero el código no es equivalente. No se puede escribir el código equivalente para la mala interpretación printf, o cualquier función variadic, mientras se sigue llamando a la función variadic. –

+0

Mi código hace (para mí, y probablemente para él) lo que su código está haciendo (para él). Ambos son comportamientos indefinidos, por lo que ninguno está garantizado para hacer algo en particular. –

7

No existe la "conversión a int en printf". printf no hace y no puede hacer ningún casting. El especificador de formato incoherente conduce a un comportamiento indefinido.

En la práctica printf se limita a recibir los datos en bruto y reinterpreta como el tipo implícito en el especificador de formato. Si le pasa un valor de double y especifica un especificador de formato int (como %d), printf tomará ese valor double y lo reinterpretará ciegamente como int. Los resultados serán completamente impredecibles (por lo que hacer esto formalmente causa un comportamiento indefinido en C).

+0

Por supuesto, podría emitir, ¿por qué no? Toda la información está allí. El compilador conoce los tipos de argumento incluso mejor que nosotros (¿alguien mencionó las promociones predeterminadas?), Y los especificadores de conversión son parte del estándar. La razón por la que no se convierte correctamente es parcialmente histórica (los compiladores de C originales eran demasiado simplistas para dicho análisis), y un cambio ahora posiblemente incluso rompería el código que se basa en la reinterpretación. –

+0

@ Peter A. Schneider: ¿De verdad? ¿Qué pasa con las situaciones en las que la cadena de formato es un valor de tiempo de ejecución? ¿Cómo espera que el compilador "proyecte" algo incluso si "los especificadores de conversión son parte del estándar"? En términos más generales, este es un principio fundamental del diseño del lenguaje C: su biblioteca estándar no se basa en las características "mágicas" del compilador (con algunas excepciones, tal vez). Prácticamente todas las características de la biblioteca pueden ser implementadas por el usuario en el lenguaje C en sí. Mientras el lenguaje C siga ese principio fundamental, 'printf' no se" aumentará "con ninguna magia del compilador. – AnT

+0

Es cierto que no pensé en cadenas de formato de tiempo de ejecución (porque en toda realidad: ¿cuándo fue la última vez que usaste una?). Pero el estándar podría distinguir los casos: mira lo que le han hecho a 'sizeof'. En cuanto al reparto: es bastante obvio que el compilador sabe cómo lanzar tipos en general, por lo que no puedo detectar ninguna magia adicional aquí; y también creo que el elenco probablemente podría implementarse en C de todos modos. Como un gran if/else si cambia los operadores de conversión, y luego los lanzamientos apropiados. El cambio radical sería más bien la semántica cambiante del prototipo de puntos suspensivos. –

1

La razón por la que su código de seguimiento funciona es porque la constante de caracteres se promueve a una int antes de que se inserte en la pila. Entonces printf aparece 4 bytes para% c y para% d. De hecho, las constantes de caracteres son de tipo int, no tipo char. C es extraño de esa manera.

0

Vale la pena señalar que printf, siendo una función con una lista de argumentos de longitud variable, nunca recibe un flotante; los argumentos flotantes son "old school" promovidos a dobles.

Un reciente proyecto de norma introduce las promociones por defecto "vieja escuela" primero (n1570, 6.5.2.2/6):

If the expression that denotes the called function has a type that does not include a prototype, the integer promotions are performed on each argument, and arguments that have type float are promoted to double. These are called the default argument promotions.

Luego se discute listas de variables de argumentos (6.5.2.2/7):

The ellipsis notation in a function prototype declarator causes argument type conversion to stop after the last declared parameter. The default argument promotions are performed on trailing arguments.

La consecuencia para printf es que es imposible "imprimir" un flotador genuino. Una expresión flotante siempre se promociona al doble, que es un valor de 8 bytes para las implementaciones IEEE 754. Esta promoción ocurre en el lado de la llamada; printf ya tendrá un argumento de 8 bytes en la pila cuando comience su ejecución.

Si asignamos 11.22 a un doble e inspeccionamos su contenido, con mi x86_64-pc-cygwin gcc veo la secuencia de bytes 000000e0a3702640.

Eso explica el valor int impreso por printf: los Ints en este destino todavía tienen 4 bytes, de modo que solo se evalúan los primeros cuatro bytes 000000e0, y de nuevo en little endian, es decir, 0xe0000000. Esto es -536870912 en decimal.

Si invertimos todos los 8 bytes, porque el procesador Intel también almacena dobles en little endian, obtenemos 402670a3e0000000. Podemos verificar el valor que esta secuencia de bytes representa en el formato IEEE on this web site; está cerca de 1.122E1, es decir, 11.22, el resultado esperado.