¿Cuándo alguien usaría una unión? ¿Es un remanente de los días C-only?

133

He aprendido pero realmente no consigo sindicatos. Cada texto C o C ++ que reviso los presenta (a veces de pasada), pero tienden a dar muy pocos ejemplos prácticos de por qué o dónde usarlos. ¿Cuándo serían útiles los sindicatos en un caso moderno (o incluso antiguo)? Mis únicas dos conjeturas serían programar microprocesadores cuando tienes un espacio muy limitado para trabajar, o cuando estás desarrollando una API (o algo similar) y quieres obligar al usuario final a tener solo una instancia de varios objetos / tipos en una vez. ¿Estas dos conjeturas están incluso cerca de la derecha?

Russel
fuente
31
C / C ++ no es un lenguaje. Los sindicatos son moderadamente útiles en C y en gran medida inútiles en C ++. Sería correcto decir que en C ++ son un "remanente de C ++ que se basa en C", pero no decir que son "un remanente de los C días solamente" como si C ++ reemplazara a C.
R .. GitHub DEJA DE AYUDAR AL HIELO el
12
¿Puede explicar qué es el sustituto de c ++ para las uniones, o por qué son inútiles en c ++?
Russel
3
El sustituto de C ++ para las uniones son las clases y la herencia: las uniones en C se usan casi exclusivamente para el polimorfismo de tipo seguro. Algo que las clases son mucho mejores. (Ver la respuesta de vz0 para el polimorfismo de estilo C)
tobyodavies
66
@R ..: la unión sigue siendo moderadamente útil en C ++. Ver las respuestas a continuación.
Michael
2
Las uniones pueden ser extraordinariamente valiosas en las entrañas de un sistema operativo o, por ejemplo, en un paquete que ensambla / desensambla archivos de sonido. En tales contextos, se usan de diferentes maneras: conversión de datos / endian, polimorfismo de bajo nivel, et al. Sí, hay otras soluciones para el mismo problema (principalmente la conversión entre tipos de puntero), pero las uniones a menudo son más limpias y se documentan mejor.
Hot Licks

Respuestas:

105

Los sindicatos se usan generalmente con la compañía de un discriminador: una variable que indica cuál de los campos del sindicato es válido. Por ejemplo, supongamos que desea crear su propio tipo de variante :

struct my_variant_t {
    int type;
    union {
        char char_value;
        short short_value;
        int int_value;
        long long_value;
        float float_value;
        double double_value;
        void* ptr_value;
    };
};

Entonces lo usarías como:

/* construct a new float variant instance */
void init_float(struct my_variant_t* v, float initial_value) {
    v->type = VAR_FLOAT;
    v->float_value = initial_value;
}

/* Increments the value of the variant by the given int */
void inc_variant_by_int(struct my_variant_t* v, int n) {
    switch (v->type) {
    case VAR_FLOAT:
        v->float_value += n;
        break;

    case VAR_INT:
        v->int_value += n;
        break;
    ...
    }
}

Esto es en realidad un lenguaje bastante común, especialmente en las partes internas de Visual Basic.

Para un ejemplo real, vea la unión SDL_Event de SDL . ( código fuente real aquí ). Hay un typecampo en la parte superior de la unión, y el mismo campo se repite en cada estructura de evento SDL_ *. Luego, para manejar el evento correcto, debe verificar el valor del typecampo.

Los beneficios son simples: hay un solo tipo de datos para manejar todos los tipos de eventos sin usar memoria innecesaria.

vz0
fuente
2
¡Excelente! En ese caso, ahora me pregunto por qué la función Sdl no solo se implementó como una jerarquía de clases. ¿Es eso para que sea compatible con C y no solo C ++?
Russel
12
Las clases de @Russel C ++ no se pueden usar desde un programa en C, pero las estructuras / uniones en C se pueden acceder fácilmente desde C ++ usando un bloque "externo" C ".
vz0
1
Este patrón variante también se usa a menudo para los intérpretes de lenguaje de programación, por ejemplo, la definición de struct objecten github.com/petermichaux/bootstrap-scheme/blob/v0.21/scheme.c
Adam Rosenfield
1
Impresionante explicación. Siempre supe qué eran los sindicatos, pero nunca vi una razón real de por qué alguien estaría tan loco como para usarlos :) Gracias por el ejemplo.
Riwalk 01 de
@ Stargazer712, búsqueda de código de Google: google.com/…
kagali-san
87

Encuentro las uniones C ++ bastante buenas. Parece que las personas generalmente solo piensan en el caso de uso en el que uno quiere cambiar el valor de una instancia de unión "en su lugar" (que, al parecer, solo sirve para ahorrar memoria o realizar conversiones dudosas).

De hecho, los sindicatos pueden ser de gran poder como herramienta de ingeniería de software, incluso cuando nunca se cambia el valor de ninguna instancia sindical .

Caso de uso 1: el camaleón

Con las uniones, puede reagrupar varias clases arbitrarias bajo una sola denominación, lo que no está exento de similitudes con el caso de una clase base y sus clases derivadas. Sin embargo, lo que cambia es lo que puede y no puede hacer con una instancia de unión determinada:

struct Batman;
struct BaseballBat;

union Bat
{
    Batman brucewayne;
    BaseballBat club;
};

ReturnType1 f(void)
{
    BaseballBat bb = {/* */};
    Bat b;
    b.club = bb;
    // do something with b.club
}

ReturnType2 g(Bat& b)
{
    // do something with b, but how do we know what's inside?
}

Bat returnsBat(void);
ReturnType3 h(void)
{
    Bat b = returnsBat();
    // do something with b, but how do we know what's inside?
}

Parece que el programador tiene que estar seguro del tipo de contenido de una instancia de unión determinada cuando quiere usarlo. Es el caso en la función fanterior. Sin embargo, si una función recibiera una instancia de unión como argumento pasado, como es el caso de garriba, entonces no sabría qué hacer con ella. Lo mismo se aplica a las funciones que devuelven una instancia de unión, vea h: ¿cómo sabe la persona que llama qué hay dentro?

Si una instancia de unión nunca se pasa como un argumento o como un valor de retorno, es probable que tenga una vida muy monótona, con picos de emoción cuando el programador elige cambiar su contenido:

Batman bm = {/* */};
Baseball bb = {/* */};
Bat b;
b.brucewayne = bm;
// stuff
b.club = bb;

Y ese es el caso de uso más (no) popular de los sindicatos. Otro caso de uso es cuando una instancia de unión viene junto con algo que le dice su tipo.

Caso de uso 2: "Encantado de conocerte, soy objectdeClass "

Supongamos que un programador elige emparejar siempre una instancia de unión con un descriptor de tipo (dejaré a discreción del lector que imagine una implementación para uno de esos objetos). Esto anula el propósito del sindicato en sí si lo que el programador quiere es ahorrar memoria y que el tamaño del descriptor de tipo no es insignificante con respecto al del sindicato. Pero supongamos que es crucial que la instancia de unión pueda pasarse como un argumento o como un valor de retorno con la persona que llama o la persona que llama sin saber lo que hay dentro.

Entonces el programador tiene que escribir un switch declaración de flujo de control para distinguir a Bruce Wayne de un palo de madera, o algo equivalente. No es tan malo cuando solo hay dos tipos de contenido en la unión, pero obviamente, la unión ya no escala.

Caso de uso 3:

Como los autores de una recomendación para el estándar ISO C ++ lo pusieron de nuevo en 2008,

Muchos dominios con problemas importantes requieren grandes cantidades de objetos o recursos de memoria limitados. En estas situaciones, conservar el espacio es muy importante, y una unión es a menudo una forma perfecta de hacerlo. De hecho, un caso de uso común es la situación en la que un sindicato nunca cambia su miembro activo durante su vida útil. Se puede construir, copiar y destruir como si fuera una estructura que contiene solo un miembro. Una aplicación típica de esto sería crear una colección heterogénea de tipos no relacionados que no se asignan dinámicamente (tal vez se construyen in situ en un mapa o miembros de una matriz).

Y ahora, un ejemplo, con un diagrama de clase UML:

muchas composiciones para clase A

La situación en inglés simple: un objeto de clase A puede tener objetos de cualquier clase entre B1, ..., Bn, y como máximo uno de cada tipo, siendo n un número bastante grande, digamos al menos 10.

No queremos agregar campos (miembros de datos) a A así:

private:
    B1 b1;
    .
    .
    .
    Bn bn;

porque n puede variar (es posible que queramos agregar clases Bx a la mezcla), y porque esto causaría un desastre con los constructores y porque los objetos A ocuparían mucho espacio.

Podríamos usar un extraño contenedor de void*punteros a Bxobjetos con moldes para recuperarlos, pero eso es fugitivo y al estilo C ... pero lo más importante es que nos dejaría con la vida útil de muchos objetos asignados dinámicamente para administrar.

En cambio, lo que se puede hacer es esto:

union Bee
{
    B1 b1;
    .
    .
    .
    Bn bn;
};

enum BeesTypes { TYPE_B1, ..., TYPE_BN };

class A
{
private:
    std::unordered_map<int, Bee> data; // C++11, otherwise use std::map

public:
    Bee get(int); // the implementation is obvious: get from the unordered map
};

Luego, para obtener el contenido de una instancia de unión data, use a.get(TYPE_B2).b2y los me gusta, donde aes una Ainstancia de clase .

Esto es aún más poderoso ya que los sindicatos no tienen restricciones en C ++ 11. Consulte el documento vinculado anteriormente o este artículo para obtener más detalles.

jrsala
fuente
Esto fue muy útil, y la serie de ese segundo artículo fue muy informativa. Gracias.
Andrew
38

Un ejemplo es en el ámbito incrustado, donde cada bit de un registro puede significar algo diferente. Por ejemplo, una unión de un número entero de 8 bits y una estructura con 8 campos de bits de 1 bit separados le permite cambiar un bit o el byte completo.

Kevin
fuente
77
Esto también es muy común en los controladores de dispositivos. Hace unos años escribí mucho código usando sindicatos como este para un proyecto. Normalmente no se recomienda, y puede ser específico del compilador en algunos casos, pero funciona.
thkala
11
No lo llamaría "no recomendado". En el espacio incrustado, a menudo es mucho más limpio y menos propenso a errores que las alternativas, que generalmente implican muchos lanzamientos explícitos, void*o máscaras y cambios.
bta
je ¿Muchos moldes explícitos? Me parece declaraciones simples como REG |= MASKy REG &= ~MASK. Si eso es propenso a errores, póngalos en un #define SETBITS(reg, mask)y #define CLRBITS(reg, mask). No confíe en el compilador para obtener los bits en un orden determinado ( stackoverflow.com/questions/1490092/… )
Michael
26

Herb Sutter escribió en GOTW hace unos seis años, con énfasis agregado:

"Pero no piense que los sindicatos son solo un remanente de tiempos anteriores. Los sindicatos son quizás los más útiles para ahorrar espacio al permitir que los datos se superpongan, y esto todavía es deseable en C ++ y en el mundo moderno de hoy. Por ejemplo, algunos de los más C ++ avanzadoLas implementaciones de bibliotecas estándar en el mundo ahora usan solo esta técnica para implementar la "optimización de cadenas pequeñas", una gran alternativa de optimización que reutiliza el almacenamiento dentro de un objeto de cadena: para cadenas grandes, el espacio dentro del objeto de cadena almacena dinámicamente el puntero habitual información de búfer y limpieza asignada, como el tamaño del búfer; para cadenas pequeñas, el mismo espacio se reutiliza para almacenar el contenido de la cadena directamente y evitar por completo cualquier asignación de memoria dinámica. Para obtener más información sobre la optimización de cadenas pequeñas (y otras optimizaciones de cadenas y pesimismo en profundidad considerable), consulte ... ".

Y para un ejemplo menos útil, vea la pregunta larga pero no concluyente gcc, alias estricto y transmisión a través de una unión .

Joseph Quinsey
fuente
23

Bueno, un ejemplo de caso de uso en el que puedo pensar es este:

typedef union
{
    struct
    {
        uint8_t a;
        uint8_t b;
        uint8_t c;
        uint8_t d;
    };
    uint32_t x;
} some32bittype;

Luego puede acceder a las partes separadas de 8 bits de ese bloque de datos de 32 bits; sin embargo, prepárate para ser potencialmente mordido por endianness.

Este es solo un ejemplo hipotético, pero cada vez que desee dividir datos en un campo en partes componentes como esta, podría usar una unión.

Dicho esto, también hay un método que es endian-safe:

uint32_t x;
uint8_t a = (x & 0xFF000000) >> 24;

Por ejemplo, dado que esa operación binaria será convertida por el compilador a la endianness correcta.


fuente
Creo que la mejor pregunta es cuándo se deben usar los sindicatos. Usted proporcionó una respuesta de dónde una unión no es la herramienta correcta, lo que creo que debería quedar más claro en esta respuesta.
Michael
15

Algunos usos para los sindicatos:

  • Proporcione una interfaz de endianness general a un host externo desconocido.
  • Manipule datos de punto flotante de arquitectura de CPU ajena, como aceptar VAX G_FLOATS desde un enlace de red y convertirlos a IEEE 754 reales reales para su procesamiento.
  • Proporcione acceso directo de giro de bits a un tipo de nivel superior.
union {
      unsigned char   byte_v[16];
      long double     ld_v;
 }

Con esta declaración, es simple mostrar los valores de bytes hexadecimales de a long double, cambiar el signo del exponente, determinar si es un valor normal o implementar aritmética doble larga para una CPU que no lo admite, etc.

  • Ahorro de espacio de almacenamiento cuando los campos dependen de ciertos valores:

    class person {  
        string name;  
    
        char gender;   // M = male, F = female, O = other  
        union {  
            date  vasectomized;  // for males  
            int   pregnancies;   // for females  
        } gender_specific_data;
    }
  • Selecciona los archivos de inclusión para usar con tu compilador. Encontrará decenas a cientos de usos de union:

    [wally@zenetfedora ~]$ cd /usr/include
    [wally@zenetfedora include]$ grep -w union *
    a.out.h:  union
    argp.h:   parsing options, getopt is called with the union of all the argp
    bfd.h:  union
    bfd.h:  union
    bfd.h:union internal_auxent;
    bfd.h:  (bfd *, struct bfd_symbol *, int, union internal_auxent *);
    bfd.h:  union {
    bfd.h:  /* The value of the symbol.  This really should be a union of a
    bfd.h:  union
    bfd.h:  union
    bfdlink.h:  /* A union of information depending upon the type.  */
    bfdlink.h:  union
    bfdlink.h:       this field.  This field is present in all of the union element
    bfdlink.h:       the union; this structure is a major space user in the
    bfdlink.h:  union
    bfdlink.h:  union
    curses.h:    union
    db_cxx.h:// 4201: nameless struct/union
    elf.h:  union
    elf.h:  union
    elf.h:  union
    elf.h:  union
    elf.h:typedef union
    _G_config.h:typedef union
    gcrypt.h:  union
    gcrypt.h:    union
    gcrypt.h:    union
    gmp-i386.h:  union {
    ieee754.h:union ieee754_float
    ieee754.h:union ieee754_double
    ieee754.h:union ieee854_long_double
    ifaddrs.h:  union
    jpeglib.h:  union {
    ldap.h: union mod_vals_u {
    ncurses.h:    union
    newt.h:    union {
    obstack.h:  union
    pi-file.h:  union {
    resolv.h:   union {
    signal.h:extern int sigqueue (__pid_t __pid, int __sig, __const union sigval __val)
    stdlib.h:/* Lots of hair to allow traditional BSD use of `union wait'
    stdlib.h:  (__extension__ (((union { __typeof(status) __in; int __i; }) \
    stdlib.h:/* This is the type of the argument to `wait'.  The funky union
    stdlib.h:   causes redeclarations with either `int *' or `union wait *' to be
    stdlib.h:typedef union
    stdlib.h:    union wait *__uptr;
    stdlib.h:  } __WAIT_STATUS __attribute__ ((__transparent_union__));
    thread_db.h:  union
    thread_db.h:  union
    tiffio.h:   union {
    wchar.h:  union
    xf86drm.h:typedef union _drmVBlank {
wallyk
fuente
55
Tsk tsk! Dos votos negativos y sin explicaciones. Eso es decepcionante.
wallyk
El ejemplo con una persona que puede sostener a un hombre y una mujer es un diseño muy malo en mis ojos. ¿Por qué no una clase base de persona y un hombre y una mujer derivaron una? Lo sentimos, pero buscar manualmente una variable para determinar el tipo almacenado en un campo de datos es una mala idea. Este es un código c hecho a mano que nunca se había visto en años. Pero no hay voto negativo, es solo mi punto de vista :-)
Klaus
44
Supongo que tienes votos negativos para el sindicato "castrado" o "embarazos". Está un poco enfermo
akaltar
2
Sí, supongo que fue un día oscuro.
wallyk
14

Las uniones son útiles cuando se trata con datos de nivel de byte (nivel bajo).

Uno de mis usos recientes fue en el modelado de direcciones IP que se ve a continuación:

// Composite structure for IP address storage
union
{
    // IPv4 @ 32-bit identifier
    // Padded 12-bytes for IPv6 compatibility
    union
    {
        struct
        {
            unsigned char _reserved[12];
            unsigned char _IpBytes[4];
        } _Raw;

        struct
        {
            unsigned char _reserved[12];
            unsigned char _o1;
            unsigned char _o2;
            unsigned char _o3;
            unsigned char _o4;    
        } _Octet;    
    } _IPv4;

    // IPv6 @ 128-bit identifier
    // Next generation internet addressing
    union
    {
        struct
        {
            unsigned char _IpBytes[16];
        } _Raw;

        struct
        {
            unsigned short _w1;
            unsigned short _w2;
            unsigned short _w3;
            unsigned short _w4;
            unsigned short _w5;
            unsigned short _w6;
            unsigned short _w7;
            unsigned short _w8;   
        } _Word;
    } _IPv6;
} _IP;
YeenFei
fuente
77
Sin embargo, tenga en cuenta que acceder a cosas sin procesar como esa no es estándar, y podría no funcionar como se espera con todos los compiladores.
nos
3
Además, es muy común ver esto usado de una manera que no garantiza la alineación, que es un comportamiento indefinido.
Mooing Duck
10

Un ejemplo cuando he usado una unión:

class Vector
{
        union 
        {
            double _coord[3];
            struct 
            {
                double _x;
                double _y; 
                double _z;
            };

        };
...
}

Esto me permite acceder a mis datos como una matriz o los elementos.

He usado una unión para que los diferentes términos apunten al mismo valor. En el procesamiento de imágenes, si estaba trabajando en columnas o ancho o el tamaño en la dirección X, puede ser confuso. Para aliviar este problema, uso una unión para saber qué descripciones van juntas.

   union {   // dimension from left to right   // union for the left to right dimension
        uint32_t            m_width;
        uint32_t            m_sizeX;
        uint32_t            m_columns;
    };

    union {   // dimension from top to bottom   // union for the top to bottom dimension
        uint32_t            m_height;
        uint32_t            m_sizeY;
        uint32_t            m_rows;
    };
DannyK
fuente
12
Tenga en cuenta que, aunque esta solución funciona en la mayoría de las plataformas observables, establecer valores en _x, _y, _z y acceder a _coord es un comportamiento indefinido. El objetivo principal de los sindicatos es preservar el espacio. Debe acceder exactamente al mismo elemento de unión que estableció previamente.
anxieux
1
así es como lo uso también, aunque utilizo un std :: array forr coords y algunos static_asserts
Viktor Sehr
1
Este código viola las estrictas reglas de alias y no debe recomendarse.
Walter
¿Existe quizás una manera de mejorar el sindicato de modo que sea confiable hacer esto?
Andrew
8

Los sindicatos proporcionan polimorfismo en C.

Conjunto nulo
fuente
18
Pensé void*que hizo eso ^^
2
@ user166390 Polymorphism está utilizando la misma interfaz para manipular múltiples tipos; void * no tiene interfaz.
Alice
2
En C, el polimorfismo se implementa comúnmente a través de tipos opacos y / o punteros de función. No tengo idea de cómo o por qué usarías un sindicato para lograrlo. Suena como una idea realmente mala.
Lundin
7

Un uso brillante de la unión es la alineación de la memoria, que encontré en el código fuente PCL (Biblioteca de nube de puntos). La estructura de datos única en la API puede apuntar a dos arquitecturas: CPU con soporte SSE y CPU sin soporte SSE. Por ejemplo: la estructura de datos para PointXYZ es

typedef union
{
  float data[4];
  struct
  {
    float x;
    float y;
    float z;
  };
} PointXYZ;

Los 3 flotadores están acolchados con un flotador adicional para la alineación SSE. Entonces para

PointXYZ point;

El usuario puede acceder a point.data [0] o point.x (según el soporte de SSE) para acceder, por ejemplo, a la coordenada x. Más detalles de uso similares están en el siguiente enlace: Documentación PCL Tipos PointT

Shubham Verma
fuente
7

La unionpalabra clave, aunque todavía se usa en C ++ 03 1 , es principalmente un remanente de los días C. El problema más evidente es que solo funciona con POD 1 .

La idea de la unión, sin embargo, todavía está presente, y de hecho las bibliotecas Boost presentan una clase similar a la unión:

boost::variant<std::string, Foo, Bar>

Que tiene la mayoría de los beneficios de union(si no todos) y agrega:

  • capacidad de usar correctamente tipos que no sean POD
  • seguridad de tipo estático

En la práctica, se ha demostrado que era equivalente a una combinación de union+ enum, y se comparó que era tan rápido (aunque boost::anyes más del ámbito dynamic_cast, ya que usa RTTI).

1 Las uniones se actualizaron en C ++ 11 ( uniones sin restricciones ) y ahora pueden contener objetos con destructores, aunque el usuario debe invocar el destructor manualmente (en el miembro de la unión actualmente activo). Todavía es mucho más fácil usar variantes.

Matthieu M.
fuente
Esto ya no es cierto en las versiones más recientes de c ++. Ver la respuesta de jrsala, por ejemplo.
Andrew
@ Andrew: Actualicé la respuesta para mencionar que C ++ 11, con uniones sin restricciones, permitía que los tipos con destructores se almacenaran en unión. Todavía mantengo mi postura de que realmente es mucho mejor usar uniones etiquetadas como boost::variantpara tratar de usar uniones por su cuenta. Hay demasiados comportamientos indefinidos en torno a los sindicatos que sus posibilidades de hacerlo bien son abismales.
Matthieu M.
3

Del artículo de Wikipedia sobre sindicatos :

La utilidad principal de una unión es conservar espacio , ya que proporciona una forma de permitir que muchos tipos diferentes se almacenen en el mismo espacio. Los sindicatos también proporcionan polimorfismo crudo . Sin embargo, no hay verificación de tipos, por lo que depende del programador asegurarse de que se acceda a los campos adecuados en diferentes contextos. El campo relevante de una variable de unión suele estar determinado por el estado de otras variables, posiblemente en una estructura de cierre.

Un idioma común de programación en C usa uniones para realizar lo que C ++ llama reinterpret_cast, asignando un campo de una unión y leyendo desde otro, como se hace en el código que depende de la representación sin formato de los valores.

thkala
fuente
2

En los primeros días de C (por ejemplo, como se documentó en 1974), todas las estructuras compartían un espacio de nombres común para sus miembros. Cada nombre de miembro estaba asociado con un tipo y un desplazamiento; si "wd_woozle" fuera un "int" en el desplazamiento 12, entonces un puntero pde cualquier tipo de estructura p->wd_woozlesería equivalente a *(int*)(((char*)p)+12). El lenguaje requería que todos los miembros de todos los tipos de estructuras tuvieran nombres únicos, excepto que permitía explícitamente la reutilización de los nombres de los miembros en los casos en que cada estructura donde se usaban los trataba como una secuencia inicial común.

El hecho de que los tipos de estructura pudieran usarse promiscuamente hizo posible que las estructuras se comportaran como si contuvieran campos superpuestos. Por ejemplo, definiciones dadas:

struct float1 { float f0;};
struct byte4  { char b0,b1,b2,b3; }; /* Unsigned didn't exist yet */

el código podría declarar una estructura de tipo "float1" y luego usar "miembros" b0 ... b3 para acceder a los bytes individuales que contiene. Cuando se cambiaba el idioma para que cada estructura recibiera un espacio de nombres separado para sus miembros, el código que se basaba en la capacidad de acceder a las cosas de varias maneras se rompería. Los valores de separar espacios de nombres para diferentes tipos de estructuras fueron suficientes para requerir que dicho código se cambiara para acomodarlo, pero el valor de tales técnicas fue suficiente para justificar la extensión del lenguaje para continuar admitiéndolo.

Código que había sido escrita para explotar la capacidad de acceder al almacenamiento dentro de una struct float1, como si se tratara de una struct byte4podría ser hecho para trabajar en el nuevo idioma mediante la adición de una declaración: union f1b4 { struct float1 ff; struct byte4 bb; };, declarar objetos de tipo union f1b4;en lugar de struct float1, y la sustitución de los accesos a f0, b0, b1, etc. . con ff.f0, bb.b0, bb.b1, etc Mientras que hay mejores maneras de dicho código podría haber sido admitidas, el unionenfoque era al menos algo viable, al menos con las interpretaciones C89 de la era de las reglas de alias.

Super gato
fuente
1

Digamos que tiene n diferentes tipos de configuraciones (solo es un conjunto de variables que definen parámetros). Al usar una enumeración de los tipos de configuración, puede definir una estructura que tenga la ID del tipo de configuración, junto con una unión de todos los diferentes tipos de configuraciones.

De esta manera, donde sea que pase la configuración puede usar la ID para determinar cómo interpretar los datos de configuración, pero si las configuraciones fueran enormes, no se vería obligado a tener estructuras paralelas para cada tipo de espacio de desperdicio potencial.

Gavin H
fuente
1

La reciente regla de aliasing introducida en la versión reciente del estándar C ha dado un impulso reciente a la importancia ya elevada de los sindicatos .

Puedes usar uniones de unión para teclear sin infringir el estándar C.
Este programa tiene un comportamiento no especificado (porque lo asumí floaty unsigned inttengo la misma duración) pero no un comportamiento indefinido (ver aquí ).

#include <stdio.h> 

union float_uint
{
    float f;
    unsigned int ui;
};

int main()
{
    float v = 241;
    union float_uint fui = {.f = v};

    //May trigger UNSPECIFIED BEHAVIOR but not UNDEFINED BEHAVIOR 
    printf("Your IEEE 754 float sir: %08x\n", fui.ui);

    //This is UNDEFINED BEHAVIOR as it violates the Strict Aliasing Rule
    unsigned int* pp = (unsigned int*) &v;

    printf("Your IEEE 754 float, again, sir: %08x\n", *pp);

    return 0;
}
Comunidad
fuente
Las reglas de acceso de tipo no se encuentran solo en versiones "recientes" del Estándar. Cada versión de la C ha incluido esencialmente las mismas reglas. Lo que ha cambiado es que los compiladores solían considerar la nota al pie de página "La intención de esta lista es especificar aquellas circunstancias en las que un objeto puede o no tener un alias". como indicando que la regla no estaba destinada a aplicarse en casos que no implicaban alias como estaba escrito , pero ahora lo tratan como una invitación a reescribir el código para crear un alias donde no hubiera habido ninguno.
supercat
1

Me gustaría agregar un buen ejemplo práctico para usar union: implementar calculadora / intérprete de fórmulas o usar algún tipo de cálculo (por ejemplo, desea usar modificables durante las partes de tiempo de ejecución de sus fórmulas informáticas - resolver ecuaciones numéricas - solo por ejemplo). Por lo tanto, es posible que desee definir números / constantes de diferentes tipos (entero, punto flotante, incluso números complejos) como este:

struct Number{
enum NumType{int32, float, double, complex}; NumType num_t;
union{int ival; float fval; double dval; ComplexNumber cmplx_val}
}

Por lo tanto, está ahorrando memoria y lo que es más importante: evita cualquier asignación dinámica para una cantidad probablemente extrema (si usa muchos números definidos en tiempo de ejecución) de objetos pequeños (en comparación con implementaciones a través de herencia de clase / polimorfismo). Pero lo que es más interesante, aún puede usar el poder del polimorfismo C ++ (si es fanático del doble despacho, por ejemplo;) con este tipo de estructura. Simplemente agregue el puntero de interfaz "ficticio" a la clase primaria de todos los tipos de números como un campo de esta estructura, señalando esta instancia en lugar de / además del tipo sin formato, o use buenos punteros de función C antiguos.

struct NumberBase
{
virtual Add(NumberBase n);
...
}
struct NumberInt: Number
{
//implement methods assuming Number's union contains int
NumberBase Add(NumberBase n);
...
}
struct NumberDouble: Number
{
 //implement methods assuming Number's union contains double
 NumberBase Add(NumberBase n);
 ...
}
//e.t.c. for all number types/or use templates
struct Number: NumberBase{
 union{int ival; float fval; double dval; ComplexNumber cmplx_val;}
 NumberBase* num_t;
 Set(int a)
 {
 ival=a;
  //still kind of hack, hope it works because derived classes of   Number    dont add any fields
 num_t = static_cast<NumberInt>(this);
 }
}

por lo que puede usar polimorfismo en lugar de verificaciones de tipo con switch (tipo), con implementación eficiente en memoria (sin asignación dinámica de objetos pequeños), si lo necesita, por supuesto.

Cerebro
fuente
Esto puede ser útil al hacer un lenguaje dinámico. El problema que creo que resolverá es modificar una variable de tipo desconocido en masa sin implementar esa modificación N veces. Las macros son horrendas para esto y la creación de plantillas es prácticamente imposible.
Andrew
0

Desde http://cplus.about.com/od/learningc/ss/lowlevel_9.htm :

Los usos de la unión son pocos y distantes entre sí. En la mayoría de las computadoras, el tamaño de un puntero y un int son generalmente los mismos, esto se debe a que ambos generalmente caben en un registro en la CPU. Entonces, si desea hacer un lanzamiento rápido y sucio de un puntero a un int o de otra manera, declare una unión.

union intptr {   int i;   int * p; }; 
union intptr x; x.i = 1000; 
/* puts 90 at location 1000 */ 
*(x.p)=90; 

Otro uso de una unión es en un comando o protocolo de mensaje donde se envían y reciben mensajes de diferentes tamaños. Cada tipo de mensaje contendrá información diferente, pero cada uno tendrá una parte fija (probablemente una estructura) y un bit de parte variable. Así es como podría implementarlo ...

struct head {   int id;   int response;   int size; }; struct msgstring50 {    struct head fixed;    char message[50]; } struct

struct msgstring80 {struct head fixed; mensaje de char [80]; }
struct msgint10 {struct head fixed; mensaje int [10]; } struct msgack {struct head fixed; int ok; } union messagetype {
struct msgstring50 m50; struct msgstring80 m80; struct msgint10 i10; struct msgack ack; }

En la práctica, aunque los sindicatos son del mismo tamaño, tiene sentido enviar solo los datos significativos y no el espacio perdido. Un msgack tiene solo 16 bytes de tamaño, mientras que un msgstring80 tiene 92 bytes. Entonces, cuando se inicializa una variable de tipo de mensaje, tiene su campo de tamaño establecido de acuerdo con el tipo que es. Esto puede ser utilizado por otras funciones para transferir el número correcto de bytes.

ανώνυμος
fuente
0

Los sindicatos proporcionan una forma de manipular diferentes tipos de datos en una sola área de almacenamiento sin incorporar información independiente de la máquina en el programa. Son análogos a los registros de variantes en pascal

Como un ejemplo como el que se puede encontrar en un administrador de tabla de símbolos del compilador, suponga que una constante puede ser un int, un flotante o un puntero de caracteres. El valor de una constante particular debe almacenarse en una variable del tipo adecuado, pero es más conveniente para la gestión de tablas si el valor ocupa la misma cantidad de almacenamiento y se almacena en el mismo lugar, independientemente de su tipo. Este es el propósito de una unión, una variable única que puede contener legítimamente uno de varios tipos. La sintaxis se basa en estructuras:

union u_tag {
     int ival;
     float fval;
     char  *sval;
} u;

La variable u será lo suficientemente grande como para contener el mayor de los tres tipos; El tamaño específico depende de la implementación. Cualquiera de estos tipos puede asignarse a u y luego usarse en expresiones, siempre que el uso sea consistente

Khushal
fuente