Respostas:
As uniões são frequentemente usadas para converter entre as representações binárias de números inteiros e flutuantes:
union
{
int i;
float f;
} u;
// Convert floating-point bits to integer:
u.f = 3.14159f;
printf("As integer: %08x\n", u.i);
Embora esse seja um comportamento tecnicamente indefinido de acordo com o padrão C (você deve ler apenas o campo que foi escrito mais recentemente), ele atuará de maneira bem definida em praticamente qualquer compilador.
Às vezes, as uniões também são usadas para implementar o pseudo-polimorfismo em C, fornecendo uma estrutura para alguma tag indicando que tipo de objeto ele contém e, em seguida, unindo os tipos possíveis:
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;
}
}
Isso permite que o tamanho struct S
seja de apenas 12 bytes, em vez de 28.
As uniões são particularmente úteis na programação incorporada ou em situações em que é necessário acesso direto ao hardware / memória. Aqui está um exemplo 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;
Então você pode acessar o registro da seguinte maneira:
reg.dword = 0x12345678;
reg.bytes.byte3 = 4;
Endianness (ordem de bytes) e arquitetura do processador são obviamente importantes.
Outro recurso útil é o 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;
Com este código, você pode acessar diretamente um único bit no endereço do registro / memória:
x = reg.bits.b2;
A programação do sistema de baixo nível é um exemplo razoável.
IIRC, usei sindicatos para quebrar os registros de hardware nos bits do componente. Portanto, você pode acessar um registro de 8 bits (como foi no dia em que fiz isso ;-) nos bits do componente.
(Eu esqueço a sintaxe exata, mas ...) Essa estrutura permitiria que um registro de controle fosse acessado como um control_byte ou através dos bits individuais. Seria importante garantir que os bits sejam mapeados para os bits de registro corretos para uma determinada endianidade.
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;
Eu já vi isso em algumas bibliotecas como um substituto para a herança orientada a objetos.
Por exemplo
Connection
/ | \
Network USB VirtualConnection
Se você deseja que a "classe" Connection seja uma das opções acima, escreva algo como:
struct Connection
{
int type;
union
{
struct Network network;
struct USB usb;
struct Virtual virtual;
}
};
Exemplo de uso na libinfinity: http://git.0x539.de/?p=infinote.git;a=blob;f=libinfinity/common/inf-session.c;h=3e887f0d63bd754c6b5ec232948027cbbf4d61fc;hb=HEAD#l74
As uniões permitem que membros de dados mutuamente exclusivos compartilhem a mesma memória. Isso é muito importante quando a memória é mais escassa, como em sistemas embarcados.
No exemplo a seguir:
union {
int a;
int b;
int c;
} myUnion;
Essa união ocupará o espaço de um único int, em vez de três valores int separados. Se o usuário definir o valor de a e, em seguida, definir o valor de b , ele substituirá o valor de a, pois ambos estão compartilhando o mesmo local de memória.
Muitos usos. Basta fazer grep union /usr/include/*
ou em diretórios semelhantes. Na maioria dos casos, o union
envoltório é um struct
e um membro da estrutura informa qual elemento da união acessar. Por exemplo, check-out man elf
para implementações da vida real.
Este é o princípio 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;
}
Aqui está um exemplo de união da minha própria base de código (da memória e parafraseada para que possa não ser exata). Foi usado para armazenar elementos de linguagem em um intérprete que eu construí. Por exemplo, o seguinte código:
set a to b times 7.
consiste nos seguintes elementos de linguagem:
Os elementos de linguagem foram definidos como #define
valores ' ', assim:
#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
e a seguinte estrutura foi usada para armazenar cada elemento:
typedef struct {
int typ;
union {
char *str;
int val;
}
} tElem;
o tamanho de cada elemento era o tamanho da união máxima (4 bytes para o tipo e 4 bytes para a união, embora esses sejam valores típicos, os tamanhos reais dependem da implementação).
Para criar um elemento "set", você usaria:
tElem e;
e.typ = ELEM_SYM_SET;
Para criar um elemento "variable [b]", você usaria:
tElem e;
e.typ = ELEM_VARIABLE;
e.str = strdup ("b"); // make sure you free this later
Para criar um elemento "constant [7]", você usaria:
tElem e;
e.typ = ELEM_CONSTANT;
e.val = 7;
e você pode expandi-lo facilmente para incluir floats ( float flt
) ou racionals ( struct ratnl {int num; int denom;}
) e outros tipos.
A premissa básica é que o str
e val
não são contíguos na memória, eles realmente se sobrepõem, portanto é uma maneira de obter uma visão diferente no mesmo bloco de memória, ilustrado aqui, onde a estrutura é baseada no local da memória 0x1010
e números inteiros e ponteiros são ambos 4 bytes:
+-----------+
0x1010 | |
0x1011 | typ |
0x1012 | |
0x1013 | |
+-----+-----+
0x1014 | | |
0x1015 | str | val |
0x1016 | | |
0x1017 | | |
+-----+-----+
Se fosse apenas uma estrutura, ficaria assim:
+-------+
0x1010 | |
0x1011 | typ |
0x1012 | |
0x1013 | |
+-------+
0x1014 | |
0x1015 | str |
0x1016 | |
0x1017 | |
+-------+
0x1018 | |
0x1019 | val |
0x101A | |
0x101B | |
+-------+
make sure you free this later
comentário deve ser removido do elemento constante?
Eu diria que facilita reutilizar a memória que pode ser usada de diferentes maneiras, ou seja, economizando memória. Por exemplo, você gostaria de criar uma estrutura "variante" capaz de salvar uma string curta e um número:
struct variant {
int type;
double number;
char *string;
};
Em um sistema de 32 bits, isso resultaria no uso de pelo menos 96 bits ou 12 bytes para cada instância de variant
.
Usando uma união, você pode reduzir o tamanho para 64 bits ou 8 bytes:
struct variant {
int type;
union {
double number;
char *string;
} value;
};
Você pode economizar ainda mais se desejar adicionar mais tipos diferentes de variáveis, etc. Pode ser verdade que você pode fazer coisas semelhantes lançando um ponteiro nulo - mas a união torna muito mais acessível e digita seguro. Essas economias não parecem enormes, mas você está economizando um terço da memória usada para todas as instâncias dessa estrutura.
É difícil pensar em uma ocasião específica em que você precisaria desse tipo de estrutura flexível, talvez em um protocolo de mensagens em que enviasse tamanhos diferentes de mensagens, mas mesmo assim provavelmente existem alternativas melhores e mais amigáveis ao programador.
Os sindicatos são um pouco como tipos variantes em outros idiomas - eles só podem conter uma coisa de cada vez, mas podem ser int, float etc., dependendo de como você a declara.
Por exemplo:
typedef union MyUnion MYUNION;
union MyUnion
{
int MyInt;
float MyFloat;
};
MyUnion conterá apenas um int OU um float, dependendo do que você definiu mais recentemente . Então, fazendo isso:
MYUNION u;
u.MyInt = 10;
u agora possui um int igual a 10;
u.MyFloat = 1.0;
u mantém agora um valor flutuante igual a 1,0. Não possui mais um int. Obviamente agora, se você tentar imprimir printf ("MyInt =% d", u.MyInt); provavelmente você receberá um erro, embora não tenha certeza do comportamento específico.
O tamanho da união é determinado pelo tamanho do seu maior campo, neste caso o flutuador.
sizeof(int) == sizeof(float)
( == 32
) normalmente.
Muitas dessas respostas tratam da transmissão de um tipo para outro. Eu uso mais de uniões com os mesmos tipos, apenas mais deles (ou seja, ao analisar um fluxo de dados serial). Eles permitem que a análise / construção de um pacote emoldurado se torne 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..
}
}
Editar O comentário sobre endianness e struct padding são válidos e grandes preocupações. Eu usei esse corpo de código quase inteiramente no software incorporado, a maioria dos quais tinha controle das duas extremidades do pipe.
Os sindicatos são ótimos. Um uso inteligente dos sindicatos que eu já vi é usá-los ao definir um evento. Por exemplo, você pode decidir que um evento é de 32 bits.
Agora, dentro desses 32 bits, você pode designar os primeiros 8 bits como um identificador do remetente do evento ... Às vezes você lida com o evento como um todo, às vezes você o disseca e compara seus componentes. os sindicatos lhe dão flexibilidade para fazer as duas coisas.
Evento de união { eventCode longo não assinado; char não assinado eventParts [4]; };
O VARIANT
que é isso usado nas interfaces COM? Ele tem dois campos - "tipo" e uma união que mantém um valor real que é tratado dependendo do campo "tipo".
Eu usei union quando estava codificando para dispositivos incorporados. Eu tenho C int que tem 16 bits de comprimento. E preciso recuperar os 8 bits mais altos e os 8 bits mais baixos quando precisar ler de / armazenar na EEPROM. Então eu usei desta maneira:
union data {
int data;
struct {
unsigned char higher;
unsigned char lower;
} parts;
};
Não requer mudança, portanto, o código é mais fácil de ler.
Por outro lado, vi algum código stl C ++ antigo que usava union para alocador stl. Se você estiver interessado, pode ler o código fonte sgi stl . Aqui está um pedaço:
union _Obj {
union _Obj* _M_free_list_link;
char _M_client_data[1]; /* The client sees this. */
};
struct
torno de seu higher
/ lower
? No momento, ambos devem apontar apenas para o primeiro byte.
Dê uma olhada nisto: Manipulação de comando de buffer X.25
Um dos muitos comandos X.25 possíveis é recebido em um buffer e tratado no local usando um UNION de todas as estruturas possíveis.
Nas versões anteriores de C, todas as declarações de estrutura compartilhavam um conjunto comum de campos. Dado:
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];};
um compilador produziria essencialmente uma tabela de tamanhos de estruturas (e possivelmente alinhamentos) e uma tabela separada de nomes, tipos e compensações de membros de estruturas. O compilador não controlava quais membros pertenciam a quais estruturas e permitiria que duas estruturas tivessem um membro com o mesmo nome somente se o tipo e o deslocamento correspondessem (como no membro q
de struct x
e struct y
). Se p fosse um ponteiro para qualquer tipo de estrutura, p-> q adicionaria o deslocamento de "q" ao ponteiro p e buscaria um "int" no endereço resultante.
Dada a semântica acima, foi possível escrever uma função que pudesse executar algumas operações úteis em vários tipos de estrutura de forma intercambiável, desde que todos os campos usados pela função estivessem alinhados com campos úteis nas estruturas em questão. Esse era um recurso útil, e alterar C para validar membros usados para acesso à estrutura em relação aos tipos de estruturas em questão significaria perdê-lo na ausência de um meio de ter uma estrutura que possa conter vários campos nomeados no mesmo endereço. A adição de tipos de "união" a C ajudou a preencher um pouco essa lacuna (embora não o IMHO, como deveria ter sido).
Uma parte essencial da capacidade dos sindicatos de preencher essa lacuna era o fato de um ponteiro para um membro do sindicato poder ser convertido em um ponteiro para qualquer união que contenha esse membro, e um ponteiro para qualquer união poderia ser convertido em um ponteiro para qualquer membro. Embora o Padrão C89 não tenha expressamente dito que converter um T*
diretamente para a U*
equivale a converter um ponteiro para qualquer tipo de união que contenha ambos T
e U
, e depois U*
converter isso para , nenhum comportamento definido da última sequência de elenco seria afetado pelo tipo de união usado e o Padrão não especificou nenhuma semântica contrária para uma conversão direta de T
para U
. Além disso, nos casos em que uma função recebeu um ponteiro de origem desconhecida, o comportamento de escrever um objeto via T*
, converter oT*
para a U*
e, em seguida, ler o objeto via U*
seria equivalente a escrever uma união via membro do tipo T
e ler como tipo U
, que seria definido em alguns casos por padrão (por exemplo, ao acessar membros da Common Initial Sequence) e definido pela implementação (em vez Indefinido) para o resto. Embora fosse raro que programas explorassem as garantias do CIS com objetos reais do tipo união, era muito mais comum explorar o fato de que ponteiros para objetos de origem desconhecida deviam se comportar como ponteiros para membros do sindicato e ter as garantias comportamentais associadas a eles.
foo
for um int
com deslocamento 8, anyPointer->foo = 1234;
significava "pegar o endereço em anyPointer, substituí-lo por 8 bytes e executar um armazenamento inteiro do valor 1234 no endereço resultante. O compilador não precisaria saber ou se importar se anyPointer
identificado qualquer tipo de estrutura foo
listado entre seus membros
anyPointer
identifica com um membro struct, como o compilador verifica essas condições to have a member with the same name only if the type and offset matched
da sua postagem?
p->foo
dependeria do tipo e deslocamento de foo
. Essencialmente, p->foo
era uma abreviação de *(typeOfFoo*)((unsigned char*)p + offsetOfFoo)
. Quanto à sua última pergunta, quando um compilador encontra uma definição de membro struct, exige que nenhum membro com esse nome exista ou que o membro com esse nome tenha o mesmo tipo e deslocamento; Eu diria que isso teria ocorrido se existisse uma definição de membro de estrutura não correspondente, mas não sei como ela lidou com erros.
Um exemplo simples e muito útil, é ....
Imagine:
você tem um uint32_t array[2]
e deseja acessar o terceiro e o quarto bytes da cadeia de bytes. você poderia fazer *((uint16_t*) &array[1])
. Mas isso infelizmente quebra as estritas regras de alias!
Mas os compiladores conhecidos permitem que você faça o seguinte:
union un
{
uint16_t array16[4];
uint32_t array32[2];
}
tecnicamente, isso ainda é uma violação das regras. mas todos os padrões conhecidos suportam esse uso.