2011-01-08 10 views
10

En C, puede convertir tanto tipos de datos simples como int, float, como punteros a estos.En C, si lanzo y desreferencia un puntero, ¿qué importa primero?

Ahora habría supuesto que si quiere convertir de un puntero a un tipo al valor de otro tipo (por ejemplo, de *float a int), el orden de conversión y desreferenciación no tiene importancia. Es decir. que para una variable float* pf, tiene (int) *pf == *((int*) pf). Algo así como la conmutatividad en matemáticas ...

Sin embargo, este no parece ser el caso. Escribí un programa de pruebas:

#include <stdio.h> 
int main(int argc, char *argv[]){ 
    float f = 3.3; 
    float* pf = &f; 
    int i1 = (int) (*pf); 
    int i2 = *((int*) pf); 
    printf("1: %d, 2: %d\n", i1, i2); 
    return 0; 
} 

y en mi sistema la salida es

1: 3, 2: 1079194419 

Así que echando el puntero parece funcionar de manera diferente desde la fundición del valor.

¿Por qué es eso? ¿Por qué la segunda versión no hace lo que creo que debería?

¿Y depende de esta plataforma, o estoy de alguna manera invocando el comportamiento indefinido?

Respuesta

11

Lo siguiente dice obtener el flotador en pf y convertirlo en un entero. La conversión aquí es una solicitud para convertir el flotante en un número entero. El compilador produce código para convertir el valor flotante en un valor entero. (Conversión de valores de coma flotante a números enteros es algo "normal" que hacer.)

int i1 = (int) (*pf); 

la siguiente dice primera fuerza al compilador a pensar puntos PF a un entero (y pasar por alto el hecho de que pf es un puntero a un flotador) y luego obtener el valor entero (pero no es un valor entero). Esto es algo extraño y peligroso de hacer. El casting en este caso IMPULSA la conversión correcta. El compilador realiza una copia simple de los bits en la memoria (producción de basura). (Y podría haber problemas de alineación de memoria también!)

int i2 = *((int*) pf); 

En la segunda sentencia, que no está "convirtiendo" punteros. Le está diciendo al compilador a qué apunta la memoria (que, en este ejemplo, es incorrecto).

Estas dos declaraciones están haciendo muy cosas diferentes!

Tenga en cuenta que c algunas veces usa la misma sintaxis para describir diferentes operaciones.

=============

Tenga en cuenta que el doble es el tipo de punto flotante por defecto en C (la biblioteca matemática suele utilizar argumentos dobles).

1

Un int no se representa en la memoria igual que un flotador. Lo que está viendo es que el compilador está pensando que el puntero está en un int y mira los 32 bits de memoria pensando que va a encontrar un int cuando de hecho está encontrando una porción indefinida del flotador, lo que le sale a algunos muy gran número.

4

Si desreferencia primero, envía a int más tarde, obtendrás el comportamiento habitual (truncamiento) de los lanzamientos desde float hasta int. Si primero se convierte en puntero a int, entonces la desreferencia, el comportamiento no está definido por el estándar. Por lo general, se manifiesta al interpretar la memoria que contiene el flotante como int. Consulte http://en.wikipedia.org/wiki/IEEE_754-2008 para saber cómo funciona esto.

+3

Suele manifestarse como una violación de "alias estricto" que da como resultado la lectura de datos incorrectos o la ausencia de datos, al menos en gcc moderno. –

+1

No se manifiesta, es una violación, y sí, gcc generalmente lo advierte al respecto (no atrapa todos los casos, afaik). Dije que no está definido, solo le expliqué lo que sucede en su caso: puede verificarlo fácilmente, p. con un pequeño fragmento de rubí '[3.3] .pack (" f "). desempaquetar (" I ") => [1079194419]' – etarion

+0

Solo para información, mi programa de ejemplo anterior compila sin advertencias bajo '-Wall', así que sí, gcc no detecta todos los casos ;-). – sleske

3

¡Por supuesto que sí! Casting le dice al compilador cómo mirar la sección de la memoria. Cuando accede a la memoria, intenta interpretar los datos en función de cómo le indicó que la mirara. Es más fácil de entender usando el siguiente ejemplo.

int main() 
{ 
    char A[] = {0, 0, 0, 1 }; 
    int p = *((int*)A); 
    int i = (int)*A; 
    printf("%d %d\n", i, p); 
    return 0; 
} 

La salida en una máquina poco endian de 32 bits será 0 16777216. Esto es porque (int*)A le dice al compilador que trate a A como un puntero a entero y, por lo tanto, cuando desreferencia A, ve los 4 bytes comenzando desde A (as sizeof(int) == 4). Después de contar el endian-dad, el contenido de los 4 bytes evaluó a 16777216. Por otro lado, *A desreferencias A para conseguir 0 y (int)*A proyecta que para obtener 0.

+0

Gracias por el ejemplo. Por cierto, creo que ni siquiera hay garantía de que 'int' sea de 32 bits, podría ser de 16 bits. Con un int de 16 bits, su ejemplo debería dar 256 (= 2^8) en arquitecturas little-endian, ¿no es así? – sleske

+0

Ha supuesto un entero de 32 bits. Actualizará la respuesta para que sea explícita. Si int es 16 bits (2 bytes), solo se usarán dos bytes (A [0], A [1]). Como A [0] = A [1] = 0, el valor entero será 0. – 341008

2

moldeada, a continuación, eliminar la referencia de los medios "pretender que soy señalando a esta otra cosa, y luego obtener la otra cosa que supuestamente se apunta a ".

Desreferencia, luego lanzar significa "obtener lo que realmente estoy apuntando, y luego convertirlo a esta otra cosa según las reglas habituales".

"Las reglas habituales" pueden cambiar los bits para obtener un valor de otro tipo que lógicamente represente el mismo valor. Pretender que estás apuntando a algo a lo que no estás apuntando en realidad no puede.

0

Según 6.5/7 del estándar, aproximadamente, valor almacenado tiene que ser compatible con el tipo efectivo o un tipo de carácter. Entonces, creo que la declaración int i2 = *((int*) pf) no está bien definida.

+0

La afirmación casi con toda seguridad está ** mal **. – davep

Cuestiones relacionadas