Propósito de las uniones en C y C++

Resuelto legends2k asked hace 14 años • 16 respuestas

He usado uniones antes cómodamente; hoy me alarmé cuando leí esta publicación y supe que este código

union ARGB
{
    uint32_t colour;

    struct componentsTag
    {
        uint8_t b;
        uint8_t g;
        uint8_t r;
        uint8_t a;
    } components;

} pixel;

pixel.colour = 0xff040201;  // ARGB::colour is the active member from now on

// somewhere down the line, without any edit to pixel

if(pixel.components.a)      // accessing the non-active member ARGB::components

en realidad es un comportamiento indefinido, es decir, leer de un miembro de la unión distinto al que se escribió recientemente conduce a un comportamiento indefinido. Si este no es el uso previsto de los sindicatos, ¿cuál es? ¿Alguien puede explicarlo detalladamente?

Actualizar:

Quería aclarar algunas cosas en retrospectiva.

  • La respuesta a la pregunta no es la misma para C y C++; mi yo más joven e ignorante lo etiquetó como C y C++.
  • Después de revisar el estándar de C++ 11, no pude decir de manera concluyente que indica que acceder/inspeccionar a un miembro sindical no activo no está definido/no especificado/definido por la implementación. Todo lo que pude encontrar fue §9.5/1:

    Si una unión de diseño estándar contiene varias estructuras de diseño estándar que comparten una secuencia inicial común, y si un objeto de este tipo de unión de diseño estándar contiene una de las estructuras de diseño estándar, se permite inspeccionar la secuencia inicial común de cualquier de miembros de estructura de diseño estándar. §9.2/19: Dos estructuras de diseño estándar comparten una secuencia inicial común si los miembros correspondientes tienen tipos compatibles con el diseño y ninguno de los miembros es un campo de bits o ambos son campos de bits con el mismo ancho para una secuencia de uno o más miembros.

  • Mientras que en C, ( C99 TC3 - DR 283 en adelante) es legal hacerlo ( gracias a Pascal Cuoq por mencionar esto). Sin embargo, intentar hacerlo aún puede generar un comportamiento indefinido , si el valor leído no es válido (lo que se denomina "representación trampa") para el tipo que se lee. De lo contrario, el valor leído está definido por la implementación.
  • C89/90 mencionó esto bajo un comportamiento no especificado (Anexo J) y el libro de K&R dice que su implementación está definida. Cita de K&R:

    Este es el propósito de una unión: una única variable que puede contener legítimamente cualquiera de varios tipos. [...] siempre que el uso sea coherente: el tipo recuperado debe ser el tipo almacenado más recientemente. Es responsabilidad del programador realizar un seguimiento de qué tipo está almacenado actualmente en una unión; los resultados dependen de la implementación si algo se almacena como un tipo y se extrae como otro.

  • Extracto del TC++PL de Stroustrup (el énfasis es mío)

    El uso de uniones puede ser esencial para la compatibilidad de los datos [...] a veces se utilizan incorrectamente para la "conversión de tipos ".

Sobre todo, esta pregunta (cuyo título permanece sin cambios desde mi pregunta) se planteó con la intención de comprender el propósito de las uniones Y no sobre lo que permite el estándar. Por ejemplo, el uso de la herencia para la reutilización de código está, por supuesto, permitido por el estándar C++, pero No era el propósito ni la intención original de introducir la herencia como una característica del lenguaje C++ . Esta es la razón por la que la respuesta de Andrey sigue siendo la aceptada.

legends2k avatar Feb 22 '10 18:02 legends2k
Aceptado

El propósito de los sindicatos es bastante obvio, pero por alguna razón la gente lo pasa por alto con bastante frecuencia.

El propósito de la unión es ahorrar memoria utilizando la misma región de memoria para almacenar diferentes objetos en diferentes momentos. Eso es todo.

Es como una habitación en un hotel. En él viven diferentes personas durante períodos de tiempo que no se superponen. Estas personas nunca se conocen y, por lo general, no saben nada entre sí. Al gestionar adecuadamente el tiempo compartido de las habitaciones (es decir, asegurándose de que no se asignen diferentes personas a una habitación al mismo tiempo), un hotel relativamente pequeño puede ofrecer alojamiento a un número relativamente grande de personas, que es lo que hacen los hoteles. son para.

Eso es exactamente lo que hace el sindicato. Si sabe que varios objetos en su programa contienen valores con tiempos de vida que no se superponen, entonces puede "fusionar" estos objetos en una unión y así ahorrar memoria. Así como una habitación de hotel tiene como máximo un inquilino "activo" en cada momento, un sindicato tiene como máximo un miembro "activo" en cada momento del programa. Sólo se puede leer el miembro "activo". Al escribir a otro miembro, cambia el estado "activo" a ese otro miembro.

Por alguna razón, este propósito original del sindicato fue "anulado" con algo completamente diferente: escribirle a un miembro de un sindicato y luego inspeccionarlo a través de otro miembro. Este tipo de reinterpretación de la memoria (también conocido como "juego de palabras tipográfico") no es un uso válido de las uniones. Generalmente conduce a un comportamiento indefinido y se describe como que produce un comportamiento definido por la implementación en C89/90.

EDITAR: El uso de uniones con fines de juego de palabras (es decir, escribir un miembro y luego leer otro) recibió una definición más detallada en uno de los Corrigenda Técnicos del estándar C99 (consulte DR#257 y DR#283 ). Sin embargo, tenga en cuenta que formalmente esto no le protege de encontrarse con un comportamiento indefinido al intentar leer una representación trampa.

AnT stands with Russia avatar Feb 22 '2010 19:02 AnT stands with Russia

Podrías usar uniones para crear estructuras como la siguiente, que contiene un campo que nos indica qué componente de la unión se usa realmente:

struct VAROBJECT
{
    enum o_t { Int, Double, String } objectType;

    union
    {
        int intValue;
        double dblValue;
        char *strValue;
    } value;
} object;
Erich Kitzmueller avatar Feb 22 '2010 11:02 Erich Kitzmueller

El comportamiento no está definido desde el punto de vista del lenguaje. Tenga en cuenta que diferentes plataformas pueden tener diferentes restricciones en la alineación y la endianidad de la memoria. El código en una máquina big endian versus little endian actualizará los valores en la estructura de manera diferente. Arreglar el comportamiento en el lenguaje requeriría que todas las implementaciones usaran la misma endianidad (y restricciones de alineación de memoria...) limitando el uso.

Si está usando C++ (está usando dos etiquetas) y realmente le importa la portabilidad, entonces puede usar la estructura y proporcionar un configurador que tome uint32_ty establezca los campos de manera adecuada mediante operaciones de máscara de bits. Lo mismo se puede hacer en C con una función.

Editar : esperaba que AProgrammer escribiera una respuesta para votar y cerrara esta. Como han señalado algunos comentarios, la endianidad se trata en otras partes del estándar permitiendo que cada implementación decida qué hacer, y la alineación y el relleno también se pueden manejar de manera diferente. Ahora bien, las estrictas reglas de alias a las que AProgrammer se refiere implícitamente son un punto importante aquí. El compilador puede hacer suposiciones sobre la modificación (o falta de modificación) de las variables. En el caso de la unión, el compilador podría reordenar las instrucciones y mover la lectura de cada componente de color sobre la escritura a la variable de color.

David Rodríguez - dribeas avatar Feb 22 '2010 11:02 David Rodríguez - dribeas

El uso más comúnunion que encuentro regularmente es el de alias .

Considera lo siguiente:

union Vector3f
{
  struct{ float x,y,z ; } ;
  float elts[3];
}

¿Qué hace esto? Permite un acceso limpio y ordenado a Vector3f vec;los miembros de un por cualquier nombre:

vec.x=vec.y=vec.z=1.f ;

o por acceso entero a la matriz

for( int i = 0 ; i < 3 ; i++ )
  vec.elts[i]=1.f;

En algunos casos, acceder por nombre es lo más claro que puedes hacer. En otros casos, especialmente cuando el eje se elige mediante programación, lo más fácil es acceder al eje mediante un índice numérico: 0 para x, 1 para y y 2 para z.

bobobobo avatar Aug 11 '2013 22:08 bobobobo