¿Por qué necesitamos sindicatos C?
¿Cuándo se deben utilizar los sindicatos? ¿Por qué los necesitamos?
Las uniones se utilizan a menudo para convertir entre representaciones binarias de números enteros y flotantes:
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 técnicamente indefinido según el estándar C (se supone que sólo debes leer el campo que se escribió más recientemente), actuará de una manera bien definida en prácticamente cualquier compilador.
Las uniones también se utilizan a veces para implementar pseudopolimorfismo en C, dándole a una estructura alguna etiqueta que indique qué tipo de objeto contiene y luego uniendo los tipos posibles:
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
sea sólo de 12 bytes, en lugar de 28.
Las uniones son particularmente útiles en programación integrada o en situaciones donde se necesita acceso directo al hardware/memoria. Aquí hay 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;
Luego puede acceder al registro de la siguiente manera:
reg.dword = 0x12345678;
reg.bytes.byte3 = 4;
Por supuesto, el endianismo (orden de bytes) y la arquitectura del procesador son importantes.
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 único bit de la dirección de registro/memoria:
x = reg.bits.b2;
La programación de sistemas de bajo nivel es un ejemplo razonable.
IIRC, he usado uniones para dividir los registros de hardware en los bits de los componentes. Entonces, puede acceder a un registro de 8 bits (como estaba el día que hice esto ;-) en los bits del componente.
(Olvidé la sintaxis exacta pero...) Esta estructura permitiría acceder a un registro de control como un byte de control o mediante bits individuales. Sería importante garantizar que los bits se correspondan con los bits de registro correctos para una endianidad determinada.
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;
Lo he visto en un par de bibliotecas como reemplazo de la herencia orientada a objetos.
P.ej
Connection
/ | \
Network USB VirtualConnection
Si desea que la "clase" de Conexión sea una de las anteriores, puede escribir algo como:
struct Connection
{
int type;
union
{
struct Network network;
struct USB usb;
struct Virtual virtual;
}
};
Ejemplo de uso en libinfinity: http://git.0x539.de/?p=infinote.git;a=blob;f=libinfinity/common/inf-session.c;h=3e887f0d63bd754c6b5ec232948027cbbf4d61fc;hb=HEAD#l74