2010-11-17 11 views
7

Mi código es esteproblema conceptual en la Unión

// using_a_union.cpp 
#include <stdio.h> 

union NumericType 
{ 
    int   iValue; 
    long  lValue; 
    double  dValue; 
}; 

int main() 
{ 
    union NumericType Values = { 10 }; // iValue = 10 
    printf("%d\n", Values.iValue); 
    Values.dValue = 3.1416; 
    printf("%d\n", Values.iValue); // garbage value 
} 

¿Por qué recibo valor de basura cuando intento imprimir Values.iValue después de hacer Values.dValue = 3.1416? Pensé que el diseño de la memoria sería como this. ¿Qué sucede con Values.iValue y Values.lValue; cuando asigno algo al Values.dValue?

+1

¿es un problema de conversión endian? ~ Definir "basura". Eso realmente nos ayudaría a responder su pregunta. – jcolebrand

+0

@drachenstern: su pregunta se ve perfectamente formulada y en forma ... mucho mejor que la habitual "muéstrame el código" que obtenemos de los recién llegados. – mpen

+0

@Mark ~ Estoy de acuerdo. Pero dado que es nuevo, podría ser bueno tener una referencia del sitio. Además, necesitamos saber qué define como "basura" y eso hubiera sido una buena inclusión. Al menos no lo vinculé directamente con el artículo de JonSkeet;) ... Lo he estado haciendo por muchos recién llegados como un poco de limpieza. Bienvenido al club, sé un buen miembro de la comunidad, ese tipo de cosas. No fue una publicación perjudicial. – jcolebrand

Respuesta

8

En un union, todos los miembros de datos se superponen. Solo puede usar un miembro de datos de una unión a la vez.

iValue, lValue y dValue ocupan el mismo espacio.

Tan pronto como se escribe en dValue, los iValue y lValue miembros ya no son utilizables: dValue solamente es utilizable.


Editar: Abordar los comentarios a continuación: No se puede escribir en uno de los miembros de datos de un sindicato y luego leer de otro miembro de datos. Hacerlo da como resultado un comportamiento indefinido. (Hay una excepción importante: puede reinterpretar cualquier objeto en C y C++ como una matriz de char. Hay otras excepciones menores, como la posibilidad de reinterpretar un entero con signo como un entero sin signo.) Puede encontrar más en la C Estándar (C99 6.5/6-7) y el Estándar C++ (C++ 03 3.10, si no recuerdo mal).

¿Podría este "trabajo" en la práctica algunas veces? Sí. Pero a menos que su compilador indique expresamente que dicha reinterpretación garantiza que funciona correctamente y especifica el comportamiento que garantiza, no puede confiar en ella.

+1

Eso no es cierto (y yo no te devolví).Los otros dos valores son de hecho utilizables. – jcolebrand

+0

@drachenstern: ¿Qué parte de mi respuesta no es cierta? Solo puede usar un miembro de datos de una unión a la vez. ¿He entendido mal la pregunta? –

+1

Una vez más, no es cierto. unión Pixel {int rgb; char r char g; char b; } Todo 'usable'. –

7

Debido floating point numbers are represented differently than integers are.

Todas esas variables ocupan la misma área de memoria (con el doble ocupando más obviamente). Si tratas de leer los primeros cuatro bytes de ese doble como int, no vas a recuperar lo que piensas. Aquí se trata del diseño de la memoria en bruto y necesita saber cómo se representan estos tipos.


EDIT: yo también he añadido (como James ya se ha señalado) que la escritura de una variable en un sindicato y luego leer desde otra no invocar un comportamiento indefinido y debe evitarse (a menos que se vuelven a interpretar los datos como una matriz de char).

0

Usted ha hecho esto:

union NumericType Values = { 10 }; // iValue = 10 
printf("%d\n", Values.iValue); 
Values.dValue = 3.1416; 

Cómo un compilador utiliza la memoria de esta unión es similar al uso de la variable con mayor tamaño y la alineación (cualquiera de ellos si hay varios), y volver a interpretar al elenco uno de los otros tipos en la unión está escrito/visitada, como en:

double dValue; // creates a variable with alignment & space 
       // as per "union Numerictype Values" 
*reinterpret_cast<int*>(&dValue) = 10; // separate step equiv. to = { 10 } 
printf("%d\n", *reinterpret_cast<int*>(dValue)); // print as int 
dValue = 3.1416;         // assign as double 
printf("%d\n", *reinterpret_cast<int*>(dValue)); // now print as int 

el problema es que en el establecimiento de dValue a 3.1416 que ha sobrescrito por completo los bits que utilizan para mantener el número 10. el nuevo valor puede parecer basura, pero es simplemente el resultado de interpretar t El primero (tamaño de int) bytes del doble 3.1416, confiando en que allí haya un valor int útil.

Si desea que las dos cosas sean independientes, configurar el doble no afecta a la int almacenada anteriormente, entonces debe usar un struct/class.

Puede ayudarle a considerar este programa:

#include <iostream> 

void print_bits(std::ostream& os, const void* pv, size_t n) 
{ 
    for (int i = 0; i < n; ++i) 
    { 
     uint8_t byte = static_cast<const uint8_t*>(pv)[i]; 
     for (int j = 0; j < 8; ++j) 
      os << ((byte & (128 >> j)) ? '1' : '0'); 
     os << ' '; 
    } 
} 

union X 
{ 
    int i; 
    double d; 
}; 

int main() 
{ 
    X x = { 10 }; 
    print_bits(std::cout, &x, sizeof x); 
    std::cout << '\n'; 
    x.d = 3.1416; 
    print_bits(std::cout, &x, sizeof x); 
    std::cout << '\n'; 
} 

Lo cual, para mí, produce esta salida:

00001010 00000000 00000000 00000000 00000000 00000000 00000000 00000000 
10100111 11101000 01001000 00101110 11111111 00100001 00001001 01000000 

Fundamentalmente, la primera mitad de cada línea se muestran los 32 bits que están utilizado para iValue: tenga en cuenta que el binario 1010 en el byte menos significativo (a la izquierda en una CPU Intel como la mía) es 10 decimal. Escribir 3.1416 cambia los 64 bits completos a un patrón que representa 3.1416 (ver http://en.wikipedia.org/wiki/Double_precision_floating-point_format). El viejo patrón 1010 se sobrescribe, golpeado, una memoria electromagnética no más.

2

Bueno, veamos un ejemplo más simple primero. La respuesta de Ed describe la parte flotante, pero ¿qué tal si examinamos cómo se almacenan primero los caracteres y los caracteres?

He aquí un ejemplo que acabo codificado por:

#include "stdafx.h" 
#include <iostream> 
using namespace std; 

union Color { 
    int value; 
    struct { 
     unsigned char R, G, B, A; 
    }; 
}; 

int _tmain(int argc, _TCHAR* argv[]) 
{ 
    Color c; 
    c.value = 0xFFCC0000; 
    cout << (int)c.R << ", " << (int)c.G << ", " << (int)c.B << ", " << (int)c.A << endl; 
    getchar(); 
    return 0; 
} 

qué se puede esperar que la salida sea?

255, 204, 0, 0

derecho?

Si un int es de 32 bits, y cada uno de los caracteres es de 8 bits, R debe corresponderse con el byte de la izquierda, G el segundo, y así sucesivamente.

Pero eso es incorrecto. Al menos en mi máquina/compilador, parece que las entradas se almacenan en orden inverso de bytes. Consigo,

0, 0, 204, 255

Así que para hacer esto dar la salida es de esperar (o la salida que hubiera esperado de todos modos), tenemos que cambiar la estructura a A,B,G,R. Esto tiene que ver con endianness.

De todos modos, no soy un experto en estas cosas, solo algo con lo que tropecé al intentar descifrar algunos binarios. El punto es que las carrozas no están necesariamente codificadas de la manera en que esperarías tampoco ... tienes que entender cómo están almacenadas internamente para entender por qué estás obteniendo esa salida.

+0

+1 agradable [8moretogo] –

Cuestiones relacionadas