Quando os sindicatos devem ser usados? Por que nós precisamos deles?
236
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:
Então você pode acessar o registro da seguinte maneira:
Endianness (ordem de bytes) e arquitetura do processador são obviamente importantes.
Outro recurso útil é o modificador de bits:
Com este código, você pode acessar diretamente um único bit no endereço do registro / memória:
fonte
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.
fonte
Eu já vi isso em algumas bibliotecas como um substituto para a herança orientada a objetos.
Por exemplo
Se você deseja que a "classe" Connection seja uma das opções acima, escreva algo como:
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
fonte
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:
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.
fonte
Muitos usos. Basta fazer
grep union /usr/include/*
ou em diretórios semelhantes. Na maioria dos casos, ounion
envoltório é umstruct
e um membro da estrutura informa qual elemento da união acessar. Por exemplo, check-outman elf
para implementações da vida real.Este é o princípio básico:
fonte
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:
consiste nos seguintes elementos de linguagem:
Os elementos de linguagem foram definidos como
#define
valores ' ', assim:e a seguinte estrutura foi usada para armazenar cada elemento:
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:
Para criar um elemento "variable [b]", você usaria:
Para criar um elemento "constant [7]", você usaria:
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
eval
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ória0x1010
e números inteiros e ponteiros são ambos 4 bytes:Se fosse apenas uma estrutura, ficaria assim:
fonte
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:
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:
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.
fonte
É 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:
MyUnion conterá apenas um int OU um float, dependendo do que você definiu mais recentemente . Então, fazendo isso:
u agora possui um int igual a 10;
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.
fonte
sizeof(int) == sizeof(float)
(== 32
) normalmente.As uniões são usadas quando você deseja modelar estruturas definidas por hardware, dispositivos ou protocolos de rede ou quando você cria um grande número de objetos e deseja economizar espaço. Você realmente não precisa deles 95% do tempo, fique com o código fácil de depurar.
fonte
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.
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.
fonte
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.
fonte
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".fonte
Na escola, usei sindicatos como este:
Usei-o para lidar com cores mais facilmente, em vez de usar os operadores >> e <<, apenas tive que passar pelo índice diferente da minha matriz de caracteres.
fonte
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:
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:
fonte
struct
torno de seuhigher
/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.
fonte
Nas versões anteriores de C, todas as declarações de estrutura compartilhavam um conjunto comum de campos. Dado:
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
destruct x
estruct 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 aU*
equivale a converter um ponteiro para qualquer tipo de união que contenha ambosT
eU
, e depoisU*
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 deT
paraU
. Além disso, nos casos em que uma função recebeu um ponteiro de origem desconhecida, o comportamento de escrever um objeto viaT*
, converter oT*
para aU*
e, em seguida, ler o objeto viaU*
seria equivalente a escrever uma união via membro do tipoT
e ler como tipoU
, 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.fonte
foo
for umint
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 seanyPointer
identificado qualquer tipo de estruturafoo
listado entre seus membrosanyPointer
identifica com um membro struct, como o compilador verifica essas condiçõesto have a member with the same name only if the type and offset matched
da sua postagem?p->foo
dependeria do tipo e deslocamento defoo
. 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:
tecnicamente, isso ainda é uma violação das regras. mas todos os padrões conhecidos suportam esse uso.
fonte