Pregunta

Tengo uniones usadas anteriormente con comodidad; hoy me alarmé cuando leí este post y llegó a saber 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
comportamiento

que realmente está sin definir es decir, la lectura de un miembro de la Unión distinto del escrito recientemente que conduce a un comportamiento indefinido. Si este no es el uso previsto de los sindicatos, lo que es? ¿Puede alguien por favor explicar detalladamente?

Actualización:

Yo quería aclarar algunas cosas en retrospectiva.

  • La respuesta a la pregunta no es lo mismo para C y C ++; mi yo más joven ignorante etiquetada como ambos C y C ++.
  • Después de fregar a través de C ++ estándar de 11 que no podía concluyente decir que llama para acceder a / inspección de un miembro de un sindicato no activo no está definido / definida aplicación / no especificado. Todo lo que pude encontrar fue §9.5 / 1:
      

    Si un sindicato-diseño estándar contiene varias estructuras estándar de diseño que comparten una secuencia inicial común, y si un objeto de este-diseño estándar tipo de unión contiene una de las estructuras de diseño de páginas estándar, se permite inspeccionar la inicial común secuencia de cualquiera de miembros de la estructura de diseño de páginas estándar. §9.2 / 19: Dos estructuras estándar de diseño compartir una secuencia inicial común si los miembros correspondientes tienen tipos compatible con la disposición y, o bien ninguno de los miembros es un campo de bits o ambos son campos de bits con la misma anchura para una secuencia de uno o más inicial miembros.

  • Mientras que en C, ( C99 TC3 - DR 283 en adelante) que es legal para hacerlo ( gracias a Pascal Cuoq de mencionar esto ahora). Sin embargo, el intento de hacer todavía puede conducir a un comportamiento indefinido , si el valor de lectura pasa a ser no válida (la llamada "trampa de representación") para el tipo que se lee a través. De lo contrario, el valor de lectura es definido por la implementación.
  • C89 / 90 llamó a este comportamiento no especificado bajo (Anexo J) y el libro de K & R dice que es definido por la implementación. Cita de K & R:

      

    Esta es la finalidad de una unión - una única variable que puede sostener legítimamente cualquiera de uno de varios tipos. [...] siempre y cuando el uso es consistente: el tipo recuperado debe ser el tipo más recientemente almacenado. Es responsabilidad del programador para no perder de vista qué tipo se encuentra actualmente almacenado en un sindicato; los resultados son dependientes de la implementación si algo se almacena como un tipo y se extrae como otro.

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

      

    El uso de uniones puede ser esencial para compatness de datos [...] a veces mal utilizados para la "conversión de tipo ".

Por encima de todo, esta pregunta (cuyo título se mantiene sin cambios desde mi ASK) se planteó con la intención de entender el propósito de los sindicatos y no en lo que el estándar permite P. ej Uso de herencia para la reutilización de código es, por supuesto, permitido por el C ++ estándar, pero que no era el propósito o la intención original de la introducción de la herencia como C ++ característica del lenguaje . Esta es la razón por la respuesta de Andrey sigue permaneciendo como la aceptada.

¿Fue útil?

Solución

El objetivo de los sindicatos es bastante obvio, pero por alguna razón la gente se pierda muy a menudo.

El propósito de la unión es para guardar la memoria mediante el uso de la misma región de memoria para almacenar diferentes objetos en diferentes momentos. Eso es todo.

Es como una habitación en un hotel. Diferentes personas viven en ella durante períodos de tiempo que no se solapan. Estas personas nunca se encuentran, y por lo general no saben nada el uno del otro. Al gestionar adecuadamente el tiempo de intercambio de las habitaciones (es decir, asegurándose de que diferentes personas no se les asignaron a una habitación al mismo tiempo), un hotel relativamente pequeño puede proporcionar alojamiento a un número relativamente grande de personas, que es lo que los hoteles son para.

Eso es exactamente lo que hace la unión. Si usted sabe que varios objetos en los valores de retención de programa con la no superposición de valor vidas, entonces se puede "combinación" de estos objetos en una unión y así ahorrar memoria. Al igual que una habitación de hotel tiene a lo sumo un inquilino "activa" en cada momento del tiempo, una unión tiene como máximo un miembro "activo" en cada momento del tiempo del programa. Sólo el miembro "activo" se puede leer. Al escribir en otro miembro se cambia el estado "activo" a ese otro miembro.

Por alguna razón, este propósito original de la unión quedó "anulado" con algo completamente diferente: escritura de un miembro de un sindicato y después de inspeccionar a través de otro miembro. Este tipo de reinterpretación de memoria (también conocido como "tipo de juegos de palabras") es no es un uso válido de los sindicatos. Por lo general, conduce a un comportamiento indefinido se describe como la producción de comportamiento definido por la implementación en C89 / 90.

EDIT: Uso de uniones a los efectos de tipo juegos de palabras (es decir, escribir un miembro y entonces la lectura de otro) fue dado una definición más detallada en una de las correcciones técnicas a la norma C99 (ver DR # 257 y DR # 283 ). Sin embargo, tenga en cuenta que esto no formalmente a protegerse de correr en un comportamiento indefinido por intentar leer una representación trampa.

Otros consejos

Se podría utilizar uniones para crear estructuras como las siguientes, que contiene un campo que nos dice qué componente de la unión de hecho se utiliza:

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

    union
    {
        int intValue;
        double dblValue;
        char *strValue;
    } value;
} object;

El comportamiento no está definido desde el punto de vista del lenguaje. Considere que las diferentes plataformas pueden tener diferentes limitaciones en la alineación de la memoria y la orden de bits. El código en un big endian frente a una pequeña máquina endian actualizará los valores de la estructura de manera diferente. La fijación de la conducta en el idioma requeriría que todas las implementaciones de utilizar el mismo orden de bits (y restricciones de alineación de memoria ...) el uso limitante.

Si está utilizando C ++ (que está utilizando dos etiquetas) y realmente se preocupan por la portabilidad, a continuación, puedes utilizar la estructura y proporcionar un regulador que lleva el uint32_t y establece los campos adecuadamente a través de operaciones de máscara de bits. El mismo se puede hacer en C con una función.

Editar : Yo estaba esperando AProgrammer para escribir una respuesta a voto y cerca de éste. Como algunos comentarios han señalado, endianness se reparte en otras partes de la norma al permitir que cada implementación decidir qué hacer, y la alineación y el relleno también puede ser manejado de manera diferente. Ahora, el aliasing estricta dictamina que AProgrammer se refiere implícitamente a son un punto importante aquí. El compilador se le permite hacer suposiciones sobre la modificación (o la falta de modificación) de las variables. En el caso de la unión, el compilador podría cambiar el orden de las instrucciones y mover la lectura de cada componente de color sobre la escritura en la variable color.

El más común uso de union vengo regularmente a través es aliasing .

Tenga en cuenta lo siguiente:

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

¿Qué hace esto? Permite limpia, ordenada el acceso de los miembros de una Vector3f vec; por o bien Nombre:

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

o por el acceso número entero en el array

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

En algunos casos, el acceso por su nombre es la cosa más clara que puede hacer. En otros casos, especialmente cuando se elige programación el eje, lo más fácil de hacer es acceder al eje por el índice numérico -. 0 para x, 1 para Y, y 2 para z

Como usted dice, este es un comportamiento estrictamente definido, a pesar de que va a "trabajar" en muchas plataformas. La verdadera razón para el uso de los sindicatos es crear registros variantes.

union A {
   int i;
   double d;
};

A a[10];    // records in "a" can be either ints or doubles 
a[0].i = 42;
a[1].d = 1.23;

Por supuesto, también es necesario algún tipo de discriminador para decir lo que en realidad contiene la variante. Y tenga en cuenta que en las uniones C ++ no son de mucha utilidad, ya que pueden sólo contienen tipos de POD -. Efectivamente los que no tienen constructores y destructores

En C era una buena manera de poner en práctica algo así como una variante.

enum possibleTypes{
  eInt,
  eDouble,
  eChar
}


struct Value{

    union Value {
      int iVal_;
      double dval;
      char cVal;
    } value_;
    possibleTypes discriminator_;
} 

switch(val.discriminator_)
{
  case eInt: val.value_.iVal_; break;

En tiempos de memoria litlle esta estructura está utilizando menos memoria que un struct que tiene todo el miembro.

Por cierto C proporciona

    typedef struct {
      unsigned int mantissa_low:32;      //mantissa
      unsigned int mantissa_high:20;
      unsigned int exponent:11;         //exponent
      unsigned int sign:1;
    } realVal;

a los valores de bit de acceso.

A pesar de que este comportamiento no está definido estrictamente, en la práctica se trabajará con casi cualquier compilador. Es como un paradigma ampliamente utilizado que cualquier compilador precie tendrá que hacer "lo correcto" en casos como este. Es ciertamente preferible a tipo-juegos de palabras, que bien puede generar código roto con algunos compiladores.

En C ++, Boost Variant implementar un seguro versión de la unión, diseñado para evitar un comportamiento indefinido tanto como sea posible.

Sus resultados son idénticos a la construcción enum + union (pila asignada demasiado etc), pero utiliza una lista plantilla de tipos en lugar de la enum:)

El comportamiento puede ser indefinido, pero eso sólo significa que no hay un "estándar". Todos los compiladores decente ofrecen #pragmas a controlar el embalaje y la alineación, pero puede tener diferentes valores predeterminados. Los valores por defecto también cambiarán dependiendo de los ajustes de optimización utilizados.

Además, los sindicatos no son solo para ahorrar espacio. Ellos pueden ayudar a los compiladores modernos con el tipo de juegos de palabras. Si reinterpret_cast<> todo lo que el compilador no puede hacer suposiciones acerca de lo que está haciendo. Puede tener que deshacerse de lo que se sabe acerca de su tipo y empezar de nuevo (forzando una reescritura de la memoria, que es muy ineficiente en estos días en comparación con la velocidad de reloj de la CPU).

Técnicamente es indefinido, pero en realidad la mayoría (todos?) Los compiladores tratarlo exactamente lo mismo que usar un reinterpret_cast de un tipo a otro, el resultado de los cuales se define la aplicación. Porque no quiero perder el sueño por encima de su código actual.

Para un ejemplo más de la utilización real de los sindicatos, el marco CORBA serializa objetos utilizando el enfoque de unión etiquetado. Todas las clases definidas por el usuario son miembros de una (enorme) unión, y un número entero identificador cuenta la demarshaller cómo interpretar la unión.

Otros han mencionado las diferencias de arquitectura (pequeños - big endian)

.

He leído el problema que ya se comparte la memoria para las variables, a continuación, escribiendo a uno, los demás cambian y, dependiendo de su tipo, el valor podría ser sin sentido.

por ejemplo.     Unión{       flotar f;       int i;     } X;

La escritura a x.i no tendría sentido si luego lee de x.f -. A menos que es lo que pretende con el fin de ver los componentes de la muestra, mantisa del flotador

Creo que también es un problema de alineación:. Si algunas variables deben ser alineados por palabras a continuación, puede que no obtenga el resultado esperado

por ejemplo.     Unión{       Char c [4];       int i;     } X;

Si, hipotéticamente, en alguna máquina un char tenía que ser alineados por palabras entonces c [0] y c [1] compartiría almacenamiento con i pero no c [2] y c [3].

En el lenguaje C, como se documentó en 1974, todos los miembros de la estructura compartió un espacio de nombres comunes, y el significado de "ptr-> miembro" fue define como la adición de la desplazamiento del miembro a "ptr" y acceder a la dirección resultante usando el Tipo de miembro. Este diseño hace posible el uso de la misma PTR con el miembro nombres tomados de diferentes definiciones de la estructura pero con el mismo offset; programadores utilizan esa capacidad para una variedad de propósitos.

Cuando miembros de la estructura se asignaron sus propios espacios de nombres, se hizo imposible para declarar dos miembros de la estructura con el mismo desplazamiento. Añadiendo a los sindicatos el lenguaje hace posible lograr la misma semántica que habían sido disponibles en versiones anteriores de la lengua (aunque la incapacidad de tener nombres exportados a un contexto que encierra todavía pueden han hecho necesario el uso de un buscar / reemplazar para reemplazar foo-> miembro en foo-> type1.member). Lo que era importante no era tanto que las personas que se agregan los sindicatos tienen ningún particular, El uso objetivo en mente, sino que proporcionan un medio por el cual los programadores que habían confiado en la semántica anteriores, para cualquier propósito , aún debe ser capaz de lograr la misma semántica, incluso si tuvieran que utilizar una diferente sintaxis para hacerlo.

Puede uso a una unión por dos razones principales:

  1. Una forma práctica de acceder a los mismos datos de diferentes maneras, como en el ejemplo
  2. Una manera de ahorrar espacio cuando hay diferentes miembros de datos de los cuales sólo uno puede ser siempre 'activo'

1 ¿Es realmente más de un corte de estilo C para escribir código de acceso directo en base sabes cómo funciona la arquitectura de la memoria del sistema de destino. Como ya se ha dicho que normalmente puede salirse con la suya si no apuntan realmente a un montón de diferentes plataformas. Creo que algunos compiladores podrían permitirá utilizar el embalaje también directivas (sé que lo hacen en estructuras)?

Un ejemplo bien de 2. se puede encontrar en el VARIANT tipo utilizado ampliamente en COM.

Como otros mencionaron, uniones combinadas con enumeraciones y envueltos en estructuras se puede utilizar para implementar uniones etiquetados. Un uso práctico es aplicar Result<T, E> de Rust, que se implementó originalmente usando un enum puro (Rust puede contener datos adicionales en las variantes de enumeración). Aquí es un ejemplo C ++:

template <typename T, typename E> struct Result {
    public:
    enum class Success : uint8_t { Ok, Err };
    Result(T val) {
        m_success = Success::Ok;
        m_value.ok = val;
    }
    Result(E val) {
        m_success = Success::Err;
        m_value.err = val;
    }
    inline bool operator==(const Result& other) {
        return other.m_success == this->m_success;
    }
    inline bool operator!=(const Result& other) {
        return other.m_success != this->m_success;
    }
    inline T expect(const char* errorMsg) {
        if (m_success == Success::Err) throw errorMsg;
        else return m_value.ok;
    }
    inline bool is_ok() {
        return m_success == Success::Ok;
    }
    inline bool is_err() {
        return m_success == Success::Err;
    }
    inline const T* ok() {
        if (is_ok()) return m_value.ok;
        else return nullptr;
    }
    inline const T* err() {
        if (is_err()) return m_value.err;
        else return nullptr;
    }

    // Other methods from https://doc.rust-lang.org/std/result/enum.Result.html

    private:
    Success m_success;
    union _val_t { T ok; E err; } m_value;
}
Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top