Usando% f para imprimir una variable entera

La salida del siguiente progtwig c es: 0.000000 ¿Hay una lógica detrás de la salida o el comstackdor de la respuesta depende o simplemente estoy obteniendo un valor de basura?

#include int main() { int x=10; printf("%f", x); return 0; } 

PD: – Sé que tratar de imprimir un valor entero usando% f es estúpido. Solo estoy preguntando esto desde un punto de vista teórico.

Del último borrador del C11 :

§7.16.1.1 / 2

 ...if type is not compatible with the type of the actual next argument (as promoted according to the default argument promotions), the behavior is undefined, except for the following cases: — one type is a signed integer type, the other type is the corresponding unsigned integer type, and the value is representable in both types; — one type is pointer to void and the other is a pointer to a character type. 

Lo más importante a recordar es que, como lo señala Chris, el comportamiento no está definido. Si esto estuviera en un progtwig real, lo único sensato sería corregir el código.

Por otro lado, observar el comportamiento del código cuyo comportamiento no está definido por el estándar de idioma puede ser instructivo (siempre que tenga cuidado de no generalizar demasiado el comportamiento).

El formato "%f" printf espera un argumento de tipo double y lo imprime en formato decimal sin exponente. Los valores muy pequeños se imprimirán como 0.000000 .

Cuando haces esto:

 int x=10; printf("%f", x); 

Podemos explicar el comportamiento visible dadas algunas suposiciones sobre la plataforma en la que se encuentra:

  • int es de 4 bytes
  • double es de 8 bytes
  • int argumentos int y double se pasan a printf usando el mismo mecanismo, probablemente en la stack

Por lo tanto, la llamada (de manera plausible) empujará el valor int 10 en la stack como una cantidad de 4 bytes, y printf tomará 8 bytes de datos de la stack y la tratará como la representación de un double . 4 bytes serán la representación de 10 (en hexadecimal, 0x0000000a ); Los otros 4 bytes serán basura, muy probablemente cero. La basura podría ser los 4 bytes de orden alto o orden bajo de la cantidad de 8 bytes. (O cualquier otra cosa; recuerde que el comportamiento no está definido).

Aquí hay un progtwig de demostración que acabo de lanzar juntos. En lugar de abusar de printf , copia la representación de un objeto int en un objeto double usando memcpy() .

 #include  #include  void print_hex(char *name, void *addr, size_t size) { unsigned char *buf = addr; printf("%s = ", name); for (int i = 0; i < size; i ++) { printf("%02x", buf[i]); } putchar('\n'); } int main(void) { int i = 10; double x = 0.0; print_hex("i (set to 10)", &i, sizeof i); print_hex("x (set to 0.0)", &x, sizeof x); memcpy(&x, &i, sizeof (int)); print_hex("x (copied from i)", &x, sizeof x); printf("x (%%f format) = %f\n", x); printf("x (%%g format) = %g\n", x); return 0; } 

La salida en mi sistema x86 es:

 i (set to 10) = 0a000000 x (set to 0.0) = 0000000000000000 x (copied from i) = 0a00000000000000 x (%f format) = 0.000000 x (%g format) = 4.94066e-323 

Como puede ver, el valor del double es muy pequeño (puede consultar una referencia en el formato de punto flotante IEEE para los detalles), lo suficientemente cerca como para que "%f" imprima como 0.000000 .

Permítanme enfatizar una vez más que el comportamiento no está definido , lo que significa específicamente que el estándar de lenguaje "no impone requisitos" al comportamiento del progtwig. Las variaciones en el orden de los bytes, en la representación de punto flotante y en las convenciones de paso de argumentos pueden cambiar los resultados dramáticamente. Incluso la optimización del comstackdor puede afectarlo; los comstackdores pueden asumir que el comportamiento de un progtwig está bien definido y realizar transformaciones basadas en esa suposición.

Entonces , siéntase libre de ignorar todo lo que he escrito aquí (excepto los párrafos primero y último).

Porque un entero 10 en binario se ve así:

 00000000 00000000 00000000 00001010 

Todo lo que printf hace es tomar la representación en memoria e intentar presentarla como un número de punto flotante IEEE 754.

Hay tres partes en un número de punto flotante (de MSB a LSB):

El signo: 1 bit
El exponente: 8 bits.
La mantisa: 23 bits.

Como un número entero 10 es solo 1010 en los bits de mantisa, es un número muy pequeño que es mucho menor que la precisión predeterminada del formato de punto flotante de printf.

El resultado no está definido.

Solo estoy preguntando esto desde un punto de vista teórico.

La excelente respuesta completa de Chris :

Lo que sucede en tu printf no está definido, pero podría ser bastante similar al código a continuación (depende de la implementación real de varargs, IIRC).

Descargo de responsabilidad: La siguiente es más una explicación “tal como si hubiera funcionado de esa manera” de lo que podría suceder en un caso de comportamiento no definido en una plataforma que en una descripción verdadera / válida que siempre ocurre en todas las plataformas.

Definir “indefinido”?

Imagina el siguiente código:

 int main() { int i = 10 ; void * pi = &i ; double * pf = (double *) pi ; /* oranges are apples ! */ double f = *pf ; /* what is the value inside f ? */ return 0; } 

Aquí, como su puntero a doble (es decir, pf ) apunta a una dirección que aloja un valor entero (es decir, i ), lo que obtendrá no está definido, y lo más probable es que sea basura.

¡Quiero ver qué hay dentro de esa memoria!

Si realmente desea ver qué hay detrás de esa basura (al depurar en algunas plataformas), pruebe el siguiente código en el que usaremos una unión para simular una parte de la memoria en la que escribiremos datos dobles o int:

 typedef union { char c[8] ; /* char is expected to be 1-byte wide */ double f ; /* double is expected to be 8-bytes wide */ int i ; /* int is expected to be 4-byte wide */ } MyUnion ; 

Los campos f e i se usan para establecer el valor, y el campo c se usa para mirar (o modificar) la memoria, byte a byte.

 void printMyUnion(MyUnion * p) { printf("[%i %i %i %i %i %i %i %i]\n" , p->c[0], p->c[1], p->c[2], p->c[3], p->c[4], p->c[5], p->c[6], p->c[7]) ; } 

La función anterior imprimirá el diseño de la memoria, byte a byte.

La función que aparece a continuación presentará el diseño de memoria de diferentes tipos de valores:

 int main() { /* this will zero all the fields in the union */ memset(myUnion.c, 0, 8 * sizeof(char)) ; printMyUnion(&myUnion) ; /* this should print only zeroes */ /* eg. [0 0 0 0 0 0 0 0] */ memset(myUnion.c, 0, 8 * sizeof(char)) ; myUnion.i = 10 ; printMyUnion(&myUnion) ; /* the representation of the int 10 in the union */ /* eg. [10 0 0 0 0 0 0 0] */ memset(myUnion.c, 0, 8 * sizeof(char)) ; myUnion.f = 10 ; printMyUnion(&myUnion) ; /* the representation of the double 10 in the union */ /* eg. [0 0 0 0 0 0 36 64] */ memset(myUnion.c, 0, 8 * sizeof(char)) ; myUnion.f = 3.1415 ; printMyUnion(&myUnion) ; /* the representation of the double 3.1415 in the union */ /* eg. [111 18 -125 -64 -54 33 9 64] */ return 0 ; } 

Nota: este código se probó en Visual C ++ 2010.

No significa que funcionará de esa manera (o en absoluto) en su plataforma, pero, por lo general, debe obtener resultados similares a los que se presentan anteriormente.

Al final, la basura es solo el conjunto de datos hexadecimales en la memoria que está mirando, pero visto como algún tipo.

Como la mayoría de los tipos tienen una representación de memoria diferente de los datos, mirar los datos en cualquier otro tipo que no sea el tipo original está obligado a tener resultados de basura (o no tan basura).

Su printf bien podría comportarse así, y así, tratar de interpretar una pieza de memoria en bruto como un doble cuando se estableció inicialmente como un int.

PD: Tenga en cuenta que, como el int y el doble tienen diferente tamaño en bytes, la basura se complica aún más, pero es sobre todo lo que describí anteriormente.

¡Pero quiero imprimir un int como doble!

¿Seriamente?

Helios propuso una solución .

 int main() { int x=10; printf("%f",(double)(x)); return 0; } 

Veamos el pseudo código para ver qué se alimenta al printf:

  /* printf("...", [[10 0 0 0]]) ; */ printf("%i",x); /* printf("...", [[10 0 0 0 ?? ?? ?? ??]]) ; */ printf("%f",x); /* printf("...", [[0 0 0 0 0 0 36 64]]) ; */ printf("%f",(double)(x)); 

Los modelos ofrecen un diseño de memoria diferente, cambiando efectivamente los datos enteros “10” a datos “10.0” dobles.

Por lo tanto, al usar “% i”, esperará algo como [[?? ?? ?? ??]], y para el primer printf, recibe [[10 0 0 0]] e interpreta correctamente como un entero.

Al usar “% f”, esperará algo como [[?? ?? ?? ?? ?? ?? ?? ??]], y recibir en la segunda impresión algo como [[10 0 0 0]], faltan 4 bytes. Entonces, los 4 últimos bytes serán datos aleatorios (probablemente los bytes “después” del [[10 0 0 0]], es decir, algo como [[10 0 0 0 ?? ?? ?? ??]]

En el último printf, la conversión cambió el tipo y, por lo tanto, la representación de la memoria en [[0 0 0 0 0 0 36 64]] y el printf lo interpretará correctamente como un doble.

esencialmente es basura. Los enteros pequeños parecen números de punto flotante no normalizados que no deberían existir.

Podrías lanzar la variable int así:

 int i = 3; printf("%f",(float)(i));