2008-10-31 30 views

Respuesta

26

Las uniones permiten que los miembros de datos que son mutuamente excluyentes compartan la misma memoria. Esto es bastante importante cuando la memoria es más escasa, como en los sistemas integrados.

En el siguiente ejemplo:

union { 
    int a; 
    int b; 
    int c; 
} myUnion; 

Esta unión se llevará hasta el espacio de un solo int, en lugar de 3 valores int separadas. Si el usuario establece el valor de a, y luego establece el valor de b, sobrescribiría el valor de a ya que ambos comparten la misma ubicación de memoria.

4

Las uniones se utilizan cuando desea modelar estructuras definidas por hardware, dispositivos o protocolos de red, o cuando está creando una gran cantidad de objetos y desea ahorrar espacio. Realmente no los necesitas el 95% de las veces, sigue con el código fácil de depurar.

216

sindicatos a menudo se utilizan para convertir entre las representaciones binarias de los números enteros y flota:

union 
{ 
    int i; 
    float f; 
} u; 

// Convert floating-point bits to integer: 
u.f = 3.14159f; 
printf("As integer: %08x\n", u.i); 

Aunque este es un comportamiento no definido técnicamente de acuerdo con el estándar de C (sólo se supone que tienes que leer el campo que era más recientemente escrito), actuará de una manera bien definida en prácticamente cualquier compilador.

sindicatos también se utilizan a veces para implementar pseudo-polimorfismo en C, dando una estructura de algunos etiqueta que indica el tipo de objeto que contiene, y luego unificante los posibles tipos de juntas:

enum Type { INTS, FLOATS, DOUBLE }; 
struct S 
{ 
    Type s_type; 
    union 
    { 
    int s_ints[2]; 
    float s_floats[2]; 
    double s_double; 
    }; 
}; 

void do_something(struct S *s) 
{ 
    switch(s->s_type) 
    { 
    case INTS: // do something with s->s_ints 
     break; 

    case FLOATS: // do something with s->s_floats 
     break; 

    case DOUBLE: // do something with s->s_double 
     break; 
    } 
} 

Esto permite que el tamaño de struct S para que tenga solo 12 bytes, en lugar de 28.

+0

debería haber u.y en lugar de u.f –

+0

¿Funciona el ejemplo que supone convertir flotante en número entero? No lo creo, ya que int y float se almacenan en diferentes formatos en la memoria. ¿Puedes explicar tu ejemplo? –

+3

@spin_eight: No está "convirtiendo" de float a int. Es más como "reinterpretar la representación binaria de un flotador como si fuera un int". La salida no es 3: http://ideone.com/MKjwon. Sin embargo, no estoy seguro de por qué Adam está imprimiendo como hexadecimal. – endolith

5

Es difícil pensar en una ocasión específica en la que necesitaría este tipo de estructura flexible, quizás en un protocolo de mensajes donde enviaría diferentes tamaños de mensajes, pero incluso entonces probablemente haya alternativas mejores y más amigables con los programadores.

Los sindicatos son un poco como los tipos de variantes en otros idiomas; solo pueden contener una cosa a la vez, pero eso podría ser un int, un flotador, etc., dependiendo de cómo lo declare.

Por ejemplo:

typedef union MyUnion MYUNION; 
union MyUnion 
{ 
    int MyInt; 
    float MyFloat; 
}; 

MyUnion sólo contendrá un int o un flotador, dependiendo de lo que establece la más reciente. Haciendo esto:

MYUNION u; 
u.MyInt = 10; 

u ahora tiene un int igual a 10;

u.MyFloat = 1.0; 

u ahora tiene un flotante igual a 1.0. Ya no tiene una int. Obviamente ahora si intentas imprimir printf ("MyInt =% d", u.MyInt); entonces es probable que obtenga un error, aunque no estoy seguro del comportamiento específico.

El tamaño de la unión está dictado por el tamaño de su campo más grande, en este caso, el flotante.

+1

'sizeof (int) == sizeof (float)' ('== 32') por lo general. –

+1

Para el registro, asignar al float e imprimir el int * no * causará un error, ya que ni el compilador ni el entorno de tiempo de ejecución * saben * qué valor es válido. La int que se imprime, por supuesto, no tendrá sentido para la mayoría de los propósitos. Simplemente será la representación de la memoria del flotador, interpretada como un int. –

1

Los sindicatos son geniales. Un uso inteligente de los sindicatos que he visto es usarlos al definir un evento. Por ejemplo, puede decidir que un evento sea de 32 bits.

Ahora, dentro de esos 32 bits, es posible que desee designar los primeros 8 bits en cuanto a un identificador del remitente del evento ... A veces se trata del evento como un todo, a veces se disecciona y se compara componentes. los sindicatos le dan la flexibilidad para hacer ambas cosas.

 
union Event 
{ 
    unsigned long eventCode; 
    unsigned char eventParts[4]; 
}; 
16

He aquí un ejemplo de una unión de mi propia base de código (de memoria y parafraseado así que puede no ser exactos). Se usó para almacenar elementos de lenguaje en un intérprete que construí. Por ejemplo, el siguiente código:

set a to b times 7. 

consta de los siguientes elementos de lenguaje:

  • símbolo [set]
  • variable de [a]
  • símbolo [a]
  • variable de [ b]
  • símbolo [tiempos]
  • constante [7]
  • [.] Símbolo

Elementos de idioma eran define como '#define' valores así:

#define ELEM_SYM_SET  0 
#define ELEM_SYM_TO   1 
#define ELEM_SYM_TIMES  2 
#define ELEM_SYM_FULLSTOP 3 
#define ELEM_VARIABLE  100 
#define ELEM_CONSTANT  101 

y la siguiente estructura fue utilizado para almacenar cada elemento:

typedef struct { 
    int typ; 
    union { 
     char *str; 
     int val; 
    } 
} tElem; 

entonces la El tamaño de cada elemento era el tamaño de la unión máxima (4 bytes para el tipo y 4 bytes para la unión, aunque esos son valores típicos, el real tamaños según la implementación).

Con el fin de crear un elemento de "ajuste", se debería utilizar:

tElem e; 
e.typ = ELEM_SYM_SET; 

Con el fin de crear una "[b] variable" elemento, se debería utilizar:

tElem e; 
e.typ = ELEM_VARIABLE; 
e.str = strdup ("b"); // make sure you free this later 

En con el fin de crear una "[7] constante" elemento, se debería utilizar:

tElem e; 
e.typ = ELEM_CONSTANT; 
e.val = 7; 

y que fácilmente podría expandirse para incluir a los flotadores (float flt) o racionales (struct ratnl {int num; int denom;}) y otros tipos.

La premisa básica es que str y val no son contiguos en la memoria, de hecho se superponen, por lo que es una forma de obtener una vista diferente en el mismo bloque de memoria, ilustrado aquí, donde la estructura se basa en la ubicación de memoria 0x1010 y enteros y los punteros son ambos 4 bytes:

 +-----------+ 
0x1010 |   | 
0x1011 | typ | 
0x1012 |   | 
0x1013 |   | 
     +-----+-----+ 
0x1014 |  |  | 
0x1015 | str | val | 
0x1016 |  |  | 
0x1017 |  |  | 
     +-----+-----+ 

Si fuera sólo en una estructura, que se vería así:

 +-------+ 
0x1010 |  | 
0x1011 | typ | 
0x1012 |  | 
0x1013 |  | 
     +-------+ 
0x1014 |  | 
0x1015 | str | 
0x1016 |  | 
0x1017 |  | 
     +-------+ 
0x1018 |  | 
0x1019 | val | 
0x101A |  | 
0x101B |  | 
     +-------+ 
+0

¿Debería eliminarse el elemento '' asegúrese de liberar este último'' del elemento constante? – Trevor

+0

Sí, @Trevor, aunque no puedo creer que sea la primera persona que lo haya visto en los últimos 4 años :-) Solucionado, y gracias por eso. – paxdiablo

105

sindicatos son particularmente útiles en programm Embedded o en situaciones donde se necesita acceso directo al hardware/memoria. Aquí está un ejemplo trivial:

typedef union 
{ 
    struct { 
     unsigned char byte1; 
     unsigned char byte2; 
     unsigned char byte3; 
     unsigned char byte4; 
    } bytes; 
    unsigned int dword; 
} HW_Register; 
HW_Register reg; 

Entonces, podrá acceder al registro de la siguiente manera:

reg.dword = 0x12345678; 
reg.bytes.byte3 = 4; 

Endianness (orden de bytes) y la arquitectura del procesador, por supuesto, importante.

Otra característica útil es el modificador de bits:

typedef union 
{ 
    struct { 
     unsigned char b1:1; 
     unsigned char b2:1; 
     unsigned char b3:1; 
     unsigned char b4:1; 
     unsigned char reserved:4; 
    } bits; 
    unsigned char byte; 
} HW_RegisterB; 
HW_RegisterB reg; 

Con este código se puede acceder directamente a un solo bit en la dirección de registro/memoria:

x = reg.bits.b2; 
1

¿Qué hay de VARIANT que se utiliza en ¿Interfaces COM? Tiene dos campos: "tipo" y una unión que contiene un valor real que se trata según el campo "tipo".

1

Utilicé la unión cuando estaba codificando para dispositivos integrados. Tengo C int que tiene 16 bits de largo. Y necesito recuperar los 8 bits más altos y los 8 bits más bajos cuando necesito leer/almacenar en EEPROM. Así que lo utilicé de esta manera:

union data { 
    int data; 
    struct { 
     unsigned char higher; 
     unsigned char lower; 
    } parts; 
}; 

No requiere cambios para que el código sea más fácil de leer.

Por otro lado, vi un viejo código stl de C++ que utilizaba la unión para stl allocator. Si está interesado, puede leer el código fuente sgi stl. Aquí es un pedazo de él:

union _Obj { 
    union _Obj* _M_free_list_link; 
    char _M_client_data[1]; /* The client sees this.  */ 
}; 
+1

¿No necesitarías una 'estructura' de agrupación alrededor de tu 'higher' /' lower'? En este momento ambos deben señalar al primer byte solamente. – Mario

+0

@Mario ah bien, solo lo escribo a mano y me olvido de él, gracias –

1
  • Un archivo que contiene diferentes tipos de registros.
  • Una interfaz de red que contiene diferentes tipos de solicitud.

Tome un vistazo a esto: X.25 buffer command handling

Una de las muchas posibles comandos X.25 se recibe en un búfer y manipulados en su lugar mediante el uso de una unión de todas las estructuras posibles.

+0

podría por favor explicar estos dos ejemplos. Quiero decir cómo están relacionados con la unión –

6

Yo diría que hace que sea más fácil reutilizar la memoria que se puede usar de diferentes maneras, es decir, guardar la memoria. P.ej.desea hacer una cierta estructura "variante" que es capaz de salvar a una cadena corta, así como un número:

struct variant { 
    int type; 
    double number; 
    char *string; 
}; 

En un sistema de 32 bits esto se traduciría en al menos 96 bits o 12 bytes que se utilizan para cada instancia de variant.

Usando una unión que puede reducir el tamaño de hasta 64 bits u 8 bytes:

struct variant { 
    int type; 
    union { 
     double number; 
     char *string; 
    } value; 
}; 

Usted es capaz de ahorrar aún más si se desea añadir más tipos diferentes variables etc. podría sea ​​cierto, que puede hacer cosas similares lanzando un puntero volador, pero la unión lo hace mucho más accesible y seguro. Estos ahorros no suenan masivos, pero está guardando un tercio de la memoria utilizada para todas las instancias de esta estructura.

20

Muchos de los usos. Solo haz grep union /usr/include/* o en directorios similares. La mayoría de los casos, el union está envuelto en un struct y un miembro de la estructura le dice a qué elemento de la unión acceder. Por ejemplo, checkout man elf para implementaciones de la vida real.

Este es el principio básico:

struct _mydata { 
    int which_one; 
    union _data { 
      int a; 
      float b; 
      char c; 
    } foo; 
} bar; 

switch (bar.which_one) 
{ 
    case INTEGER : /* access bar.foo.a;*/ break; 
    case FLOATING : /* access bar.foo.b;*/ break; 
    case CHARACTER: /* access bar.foo.c;*/ break; 
} 
+0

¡Exactamente lo que estaba buscando! Muy útil para reemplazar algunos parámetros de elipsis :) –

1

En la escuela, solía sindicatos como este:

typedef union 
{ 
    unsigned char color[4]; 
    int  new_color; 
}  u_color; 

lo utilicé para manejar más fácilmente los colores, en vez de usar >> y < < operadores, solo tuve que pasar por el índice diferente de mi matriz de caracteres.

28

Lo he visto en un par de bibliotecas como reemplazo de herencia orientada a objetos.

E.g.

 Connection 
    /  |  \ 
    Network USB  VirtualConnection 

Si desea que la conexión "clase" para ser uno cualquiera de los anteriores, se podría escribir algo como:

struct Connection 
{ 
    int type; 
    union 
    { 
     struct Network network; 
     struct USB usb; 
     struct Virtual virtual; 
    } 
}; 

Ejemplo utilizar en libinfinity: http://git.0x539.de/?p=infinote.git;a=blob;f=libinfinity/common/inf-session.c;h=3e887f0d63bd754c6b5ec232948027cbbf4d61fc;hb=HEAD#l74

48

Bajo nivel de la programación del sistema es un ejemplo razonable.

IIRC, he usado uniones para descomponer registros de hardware en los bits de componentes. Entonces, puede acceder a un registro de 8 bits (como lo fue, en el día en que hice esto ;-) en los bits de los componentes.

(Olvidé la sintaxis exacta pero ...) Esta estructura permitiría acceder a un registro de control como control_byte o mediante bits individuales. Sería importante asegurar que los bits se correlacionen con los bits de registro correctos para una endianidad dada.

typedef union { 
    unsigned char control_byte; 
    struct { 
     unsigned int nibble : 4; 
     unsigned int nmi  : 1; 
     unsigned int enabled : 1; 
     unsigned int fired : 1; 
     unsigned int control : 1; 
    }; 
} ControlRegister; 
+2

¡Este es un excelente ejemplo! Aquí hay un ejemplo de cómo puede utilizar esta técnica en un software integrado: http://www.edn.com/design/integrated-circuit-design/4394915/Managing-the-8--to-32-bit-processor- migration – rzetterberg

0

Un ejemplo sencillo y muy útiles, es ....

Imagínese:

que tienen una uint32_t array[2] y desea acceder a los días 3 y 4 bytes de la cadena de bytes. puedes hacer *((uint16_t*) &array[1]). ¡Pero esto lamentablemente rompe las estrictas reglas de aliasing!

Pero conocidos compiladores le permiten hacer lo siguiente:

union un 
{ 
    uint16_t array16[4]; 
    uint32_t array32[2]; 
} 

técnicamente esto sigue siendo una violación de las reglas. pero todos los estándares conocidos respaldan este uso.

3

Muchas de estas respuestas se refieren a la conversión de un tipo a otro. Obtengo el mayor uso de las uniones con los mismos tipos solo un poco más (es decir, al analizar un flujo de datos en serie). Permiten que el análisis/construcción de un enmarcado sea trivial.

typedef union 
{ 
    UINT8 buffer[PACKET_SIZE]; // Where the packet size is large enough for 
           // the entire set of fields (including the payload) 

    struct 
    { 
     UINT8 size; 
     UINT8 cmd; 
     UINT8 payload[PAYLOAD_SIZE]; 
     UINT8 crc; 
    } fields; 

}PACKET_T; 

// This should be called every time a new byte of data is ready 
// and point to the packet's buffer: 
// packet_builder(packet.buffer, new_data); 

void packet_builder(UINT8* buffer, UINT8 data) 
{ 
    static UINT8 received_bytes = 0; 

    // All range checking etc removed for brevity 

    buffer[received_bytes] = data; 
    received_bytes++; 

    // Using the struc only way adds lots of logic that relates "byte 0" to size 
    // "byte 1" to cmd, etc... 
} 

void packet_handler(PACKET_T* packet) 
{ 
    // Process the fields in a readable manner 
    if(packet->fields.size > TOO_BIG) 
    { 
     // handle error... 
    } 

    if(packet->fields.cmd == CMD_X) 
    { 
     // do stuff.. 
    } 
} 

Edición El comentario sobre endianness y el relleno estructura son preocupaciones válidas, y grandes,. He utilizado este cuerpo de código casi por completo en software integrado, la mayoría de los cuales tenía el control de ambos extremos de la tubería.

+1

Este código no funcionará (la mayoría de las veces) si los datos se intercambian en 2 plataformas diferentes por las siguientes razones: 1) Endianness puede ser diferente. 2) Relleno en estructuras. – Mahori

+0

@Ravi Estoy de acuerdo con las preocupaciones sobre endianness y relleno.Sin embargo, se debe saber que he usado esto exclusivamente en proyectos integrados. La mayoría de los cuales controlé ambos extremos de las tuberías. –

1

En las primeras versiones de C, todas las declaraciones de estructura compartirían un conjunto común de campos. Teniendo en cuenta:

struct x {int x_mode; int q; float x_f}; 
struct y {int y_mode; int q; int y_l}; 
struct z {int z_mode; char name[20];}; 

un compilador esencialmente produciría una tabla de las estructuras de los tamaños (y posiblemente alineaciones), y una tabla separada de las estructuras de los nombres, tipos y las compensaciones de los miembros. El compilador no realizó un seguimiento de qué miembros pertenecían a qué estructuras, y permitiría que dos estructuras tuvieran un miembro con el mismo nombre solo si el tipo y el desplazamiento coincidían (como con el miembro q de struct x y struct y). Si p fuera un puntero a cualquier tipo de estructura, p-> q agregaría el desplazamiento de "q" al puntero py obtendría un "int" de la dirección resultante.

Dada la semántica anterior, fue posible escribir una función que podría realizar algunas operaciones útiles en múltiples tipos de estructuras indistintamente, siempre que todos los campos utilizados por la función se alineen con campos útiles dentro de las estructuras en cuestión. Esta fue una característica útil, y cambiar C para validar los miembros utilizados para el acceso a la estructura contra los tipos de las estructuras en cuestión habría significado perderlo en ausencia de un medio para tener una estructura que pueda contener múltiples campos con nombre en la misma dirección. Agregar tipos de "unión" a C ayudó a llenar esa brecha un poco (aunque no, en mi humilde opinión, así como debería haber sido).

Una parte esencial de la capacidad de los sindicatos para cubrir esa brecha es el hecho de que un puntero a un miembro del sindicato podría convertirse en un puntero a cualquier unión que contenga ese miembro, y un puntero a cualquier unión podría convertirse en un puntero a cualquier miembro. Mientras que el Estándar C89 no dijo expresamente que lanzar un T* directamente a un U* era equivalente a convertirlo a un puntero a cualquier tipo de unión que contenga tanto T como U, y luego convertirlo a U*, no se definió el comportamiento de la última secuencia de conversión se vería afectado por el tipo de unión utilizado, y el Estándar no especificó ninguna semántica contraria para un lanzamiento directo de T a U. Además, en casos donde una función recibió un puntero de origen desconocido, el comportamiento de escribir un objeto a través de T*, convirtiendo el T* a U*, y luego leyendo el objeto a través de U* sería equivalente a escribir un sindicato a través del miembro del tipo T y leyendo como tipo U, que se definiría de forma estándar en unos pocos casos (por ejemplo, al acceder a miembros de Secuencia inicial común) y definido por la implementación (en lugar de Indefinido) para el resto.Si bien era raro que los programas explotaran las garantías CIS con objetos reales de tipo sindical, era mucho más común explotar el hecho de que los punteros a objetos de origen desconocido debían comportarse como indicadores para los miembros del sindicato y tenían las garantías de comportamiento asociadas.

Cuestiones relacionadas