Quando alguém usaria um sindicato? É um remanescente dos dias somente C?

133

Eu aprendi, mas realmente não tenho sindicatos. Todo texto em C ou C ++ que eu passo apresento os apresenta (às vezes de passagem), mas eles tendem a dar muito poucos exemplos práticos de por que ou onde usá-los. Quando os sindicatos seriam úteis em um caso moderno (ou até legado)? Minhas duas únicas suposições seriam programar microprocessadores quando você tiver um espaço muito limitado para trabalhar ou quando estiver desenvolvendo uma API (ou algo semelhante) e desejar forçar o usuário final a ter apenas uma instância de vários objetos / tipos em um tempo. Essas duas suposições estão quase certas?

Russel
fonte
31
C / C ++ não é uma linguagem. As uniões são moderadamente úteis em C e amplamente inúteis em C ++. Seria correto dizer que em C ++ eles são "remanescentes de C ++ baseados em C", mas não dizem que são "remanescentes de apenas C dias", como se C ++ substituísse C.
R .. GitHub Pare de ajudar o gelo
12
Você pode elaborar o que é o substituto de c ++ para uniões ou por que eles são inúteis em c ++?
Russel
3
O substituto do C ++ para uniões é classe e herança - as uniões em C são quase exclusivamente usadas para polimorfismo de tipo seguro. Algo que as aulas são muito melhores. (Veja a resposta da vz0 para o polimorfismo no estilo C)
tobyodavies
6
@R ..: union ainda são moderadamente úteis em C ++. Veja as respostas abaixo.
Michael
2
Os sindicatos podem ser extraordinariamente valiosos nas entranhas de um sistema operacional ou, por exemplo, em um pacote que monta / desmonta arquivos de som. Em tais contextos, eles são usados ​​de várias maneiras diferentes - conversão de dados / endian, polimorfismo de baixo nível, et al. Sim, existem outras soluções para o mesmo problema (principalmente a conversão entre tipos de ponteiros), mas os sindicatos geralmente são mais limpos e melhor se auto-documentam.
Hot Licks

Respostas:

105

Os sindicatos geralmente são usados ​​com a companhia de um discriminador: uma variável que indica qual dos campos da união é válido. Por exemplo, digamos que você queira criar seu próprio 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;
    };
};

Então você usaria 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;
    ...
    }
}

Este é realmente um idioma bastante comum, especialmente em internos do Visual Basic.

Para um exemplo real, consulte a união SDL_Event da SDL . ( código fonte atual aqui ). Há um typecampo na parte superior da união e o mesmo campo é repetido em cada estrutura de evento SDL_ *. Em seguida, para manipular o evento correto, você precisa verificar o valor do typecampo.

Os benefícios são simples: existe um único tipo de dados para lidar com todos os tipos de eventos sem usar memória desnecessária.

vz0
fonte
2
Ótimo! Nesse caso, agora estou me perguntando por que a função Sdl não foi implementada apenas como uma hierarquia de classes. Isso é para torná-lo compatível com C e não apenas com C ++?
Russel
12
As classes @Russel C ++ não podem ser usadas a partir de um programa C, mas as estruturas / uniões C podem ser acessadas facilmente pelo C ++ usando um bloco 'externo "C"'.
vz0
1
Este padrão variante é também frequentemente usado para intérpretes de linguagem de programação, por exemplo, a definição de struct objectem github.com/petermichaux/bootstrap-scheme/blob/v0.21/scheme.c
Adam Rosenfield
1
Explicação impressionante. Eu sempre soube o que eram os sindicatos, mas nunca vi uma razão real do por que alguém seria louco o suficiente para usá-los :) Obrigado pelo exemplo.
Rjalk
@ Stargazer712, a pesquisa de código do Google: google.com/...
kagali-san
87

Acho as uniões C ++ bastante legais. Parece que as pessoas geralmente pensam apenas no caso de uso em que se deseja alterar o valor de uma instância de união "no local" (que, ao que parece, serve apenas para economizar memória ou realizar conversões duvidosas).

De fato, os sindicatos podem ter grande poder como ferramenta de engenharia de software, mesmo quando você nunca altera o valor de nenhuma instância de união .

Caso de uso 1: o camaleão

Com os sindicatos, é possível reagrupar várias classes arbitrárias sob uma denominação, o que não deixa de ter semelhanças com o caso de uma classe base e de suas classes derivadas. O que muda, no entanto, é o que você pode e não pode fazer com uma determinada instância de união:

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 o programador precisa ter certeza do tipo de conteúdo de uma determinada instância de união quando deseja usá-lo. É o caso da função facima. No entanto, se uma função receber uma instância de união como um argumento passado, como é o caso gacima, ela não saberá o que fazer com ela. O mesmo se aplica às funções que retornam uma instância de união, veja h: como o chamador sabe o que está dentro?

Se uma instância de união nunca é passada como argumento ou como valor de retorno, é provável que tenha uma vida muito monótona, com picos de excitação quando o programador optar por alterar seu conteúdo:

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

E esse é o caso de uso mais (des) popular dos sindicatos. Outro caso de uso é quando uma instância de união vem com algo que informa seu tipo.

Caso de uso 2: "Prazer em conhecê-lo, eu sou objectde Class"

Suponha que um programador eleito para sempre emparelhar uma instância de união com um descritor de tipo (deixarei ao critério do leitor imaginar uma implementação para um desses objetos). Isso anula o objetivo da própria união se o que o programador deseja é economizar memória e que o tamanho do descritor de tipo não seja desprezível em relação ao da união. Mas vamos supor que seja crucial que a instância da união possa ser passada como argumento ou como valor de retorno com o receptor ou chamador sem saber o que está dentro.

Em seguida, o programador deve escrever uma switchdeclaração de fluxo de controle para diferenciar Bruce Wayne de uma vara de madeira ou algo equivalente. Não é tão ruim quando existem apenas dois tipos de conteúdo no sindicato, mas obviamente o sindicato não é mais escalável.

Caso de uso 3:

Como os autores de uma recomendação para a norma ISO C ++ colocaram em 2008,

Muitos domínios de problemas importantes requerem um grande número de objetos ou recursos limitados de memória. Nessas situações, conservar o espaço é muito importante, e uma união costuma ser a maneira perfeita de fazer isso. De fato, um caso de uso comum é a situação em que uma união nunca muda seu membro ativo durante sua vida útil. Ele pode ser construído, copiado e destruído como se fosse uma estrutura contendo apenas um membro. Uma aplicação típica disso seria criar uma coleção heterogênea de tipos não relacionados que não são alocados dinamicamente (talvez eles sejam construídos no local em um mapa ou membros de uma matriz).

E agora, um exemplo, com um diagrama de classes UML:

muitas composições para a classe A

A situação em inglês simples: um objeto da classe A pode ter objetos de qualquer classe entre B1, ..., Bn e no máximo um de cada tipo, com n sendo um número bastante grande, digamos pelo menos 10.

Não queremos adicionar campos (membros de dados) a A da seguinte maneira:

private:
    B1 b1;
    .
    .
    .
    Bn bn;

porque n pode variar (podemos adicionar classes Bx à mistura) e porque isso causaria uma confusão nos construtores e porque os objetos A ocupariam muito espaço.

Poderíamos usar um contêiner maluco de void*ponteiros para Bxobjetos com moldes para recuperá-los, mas isso é fugaz e, portanto, no estilo C ... mas mais importante que nos deixaria com a vida útil de muitos objetos alocados dinamicamente para gerenciar.

Em vez disso, o que pode ser feito é o seguinte:

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
};

Em seguida, para obter o conteúdo de uma instância de união data, use a.get(TYPE_B2).b2e os gostos, onde aé uma Ainstância de classe .

Isso é ainda mais poderoso, pois os sindicatos são irrestritos no C ++ 11. Consulte o documento vinculado acima ou este artigo para obter detalhes.

jrsala
fonte
Isso foi muito útil e a série do segundo artigo foi muito informativa. Obrigado.
Andrew
38

Um exemplo está no domínio incorporado, onde cada bit de um registro pode significar algo diferente. Por exemplo, uma união de um número inteiro de 8 bits e uma estrutura com 8 campos de bits separados de 1 bit permite alterar um bit ou o byte inteiro.

Kevin
fonte
7
Isso também é muito comum em drivers de dispositivo. Alguns anos atrás, escrevi muito código usando uniões como esta para um projeto. Normalmente não é recomendado e, em alguns casos, pode ser específico do compilador, mas funciona.
thkala
11
Eu não chamaria isso de "não recomendado". No espaço incorporado, muitas vezes é muito mais limpo e menos propenso a erros do que as alternativas, que geralmente envolvem muitos elencos void*es explícitos ou máscaras e mudanças.
BTA
heh Muitos elencos explícitos? Parece-me declarações simples como REG |= MASKe REG &= ~MASK. Se isso for propenso a erros, coloque-os em um #define SETBITS(reg, mask)e #define CLRBITS(reg, mask). Não confie em que o compilador para obter os bits em uma determinada ordem ( stackoverflow.com/questions/1490092/... )
Michael
26

Herb Sutter escreveu no GOTW cerca de seis anos atrás, com ênfase adicionada:

"Mas não pense que os sindicatos são apenas uma remanescente dos tempos anteriores. Os sindicatos talvez sejam mais úteis para economizar espaço, permitindo a sobreposição de dados, e isso ainda é desejável no C ++ e no mundo moderno de hoje. Por exemplo, alguns dos mais avançado C ++As implementações de bibliotecas padrão no mundo agora usam apenas essa técnica para implementar a "otimização de pequenas cadeias", uma ótima alternativa de otimização que reutiliza o armazenamento dentro de um objeto de cadeia: para cadeias grandes, o espaço dentro do objeto armazena o ponteiro usual no dinamicamente buffer alocado e informações de manutenção, como o tamanho do buffer; para cadeias pequenas, o mesmo espaço é reutilizado para armazenar o conteúdo diretamente e evitar completamente qualquer alocação de memória dinâmica. Para saber mais sobre a otimização de cadeias pequenas (e outras otimizações e pessimizações de cadeias com profundidade considerável), consulte ... ".

E para um exemplo menos útil, veja a pergunta longa, mas inconclusiva gcc, alias estrito e transmissão através de um sindicato .

Joseph Quinsey
fonte
23

Bem, um exemplo de caso de uso em que posso pensar é o seguinte:

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

Você pode acessar as partes separadas de 8 bits desse bloco de dados de 32 bits; no entanto, prepare-se para ser potencialmente picado por endianness.

Este é apenas um exemplo hipotético, mas sempre que você deseja dividir dados em um campo em partes componentes como essa, você pode usar uma união.

Dito isto, há também um método que é endian-safe:

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

Por exemplo, uma vez que essa operação binária será convertida pelo compilador na endianness correta.


fonte
Penso que a melhor pergunta é quando devemos usar os sindicatos. Você forneceu uma resposta sobre onde um sindicato não é a ferramenta correta, o que acho que deve ficar mais claro nessa resposta.
Michael
15

Alguns usos para sindicatos:

  • Forneça uma interface de endianness geral para um host externo desconhecido.
  • Manipule dados de ponto flutuante da arquitetura de CPU estrangeira, como aceitar VAX G_FLOATS de um link de rede e convertê-los em reais longos IEEE 754 para processamento.
  • Forneça acesso direto de bits simples a um tipo de nível superior.
union {
      unsigned char   byte_v[16];
      long double     ld_v;
 }

Com esta declaração, é simples exibir os valores de bytes hexadecimais de a long double, alterar o sinal do expoente, determinar se é um valor desnormal ou implementar aritmética dupla longa para uma CPU que não o suporta, etc.

  • Economizando espaço de armazenamento quando os campos dependem de certos 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;
    }
  • Grep os arquivos de inclusão para uso com seu compilador. Você encontrará dezenas a centenas 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
fonte
5
Tsk tsk! Dois votos negativos e nenhuma explicação. Isso é decepcionante.
amigos estão dizendo sobre wallyk
O exemplo de uma pessoa que pode segurar um homem e uma mulher é um péssimo design aos meus olhos. Por que não uma classe base de pessoa e um homem e mulher derivam uma? Desculpe, mas procurar manualmente uma variável para determinar o tipo armazenado em um campo de dados é uma má idéia. Este é um código c artesanal, nunca visto por anos. Mas não downvote, é apenas meu ponto de vista :-)
Klaus
4
Eu acho que você recebeu votos negativos para o sindicato "castrado" ou "gravidezes". Está um pouco doente.
Akaltar 06/07/19
2
Sim, acho que foi um dia escuro.
wallyk
14

As uniões são úteis ao lidar com dados em nível de bytes (nível baixo).

Um dos meus usos recentes foi na modelagem de endereços IP, que se parece com abaixo:

// 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
fonte
7
No entanto, lembre-se de que acessar material bruto como esse não é padrão e pode não funcionar como esperado com todos os compiladores.
Nos
3
Além disso, é muito comum ver isso ser usado de uma maneira que não garanta o alinhamento, que é um comportamento indefinido.
Mooing Duck
10

Um exemplo quando eu usei uma união:

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

        };
...
}

isso me permite acessar meus dados como uma matriz ou os elementos.

Eu usei uma união para que os termos diferentes aponte para o mesmo valor. No processamento de imagens, esteja eu trabalhando em colunas ou largura ou no tamanho na direção X, isso pode se tornar confuso. Para solucionar esse problema, eu uso uma união para saber quais descrições estão 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
fonte
12
Observe que, embora essa solução funcione na maioria das plataformas observáveis, definir valores como _x, _y, _z e acessar _coord é um comportamento indefinido. O principal objetivo dos sindicatos é a preservação do espaço. Você deve acessar exatamente o mesmo elemento de união que você definiu anteriormente.
anxieux
1
isto é como eu usá-lo também, althrough eu uso um std :: variedade forr coords, e alguns static_asserts
Viktor Sehr
1
Este código viola as regras estritas de alias e não deve ser recomendado.
26515 Walter Walter
Existe talvez uma maneira de melhorar a união, de modo que seja confiável fazer isso?
Andrew
8

As uniões fornecem polimorfismo em C.

Conjunto Nulo
fonte
18
Eu pensei que void*fez isso ^^
2
@ user166390 O polimorfismo está usando a mesma interface para manipular vários tipos; void * não tem interface.
Alice
2
Em C, o polimorfismo é comumente implementado através de tipos opacos e / ou ponteiros de função. Não tenho idéia de como ou por que você usaria um sindicato para conseguir isso. Parece uma ideia genuinamente ruim.
Lundin
7

Um uso brilhante da união é o alinhamento de memória, encontrado no código-fonte PCL (Point Cloud Library). A estrutura de dados única na API pode ter como alvo duas arquiteturas: CPU com suporte a SSE e CPU sem suporte a SSE. Por exemplo: a estrutura de dados do PointXYZ é

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

Os 3 flutuadores são preenchidos com um flutuador adicional para o alinhamento do SSE. Então para

PointXYZ point;

O usuário pode acessar point.data [0] ou point.x (dependendo do suporte do SSE) para acessar, por exemplo, a coordenada x. Mais detalhes semelhantes sobre o uso estão disponíveis no seguinte link: Documentação PCL Tipos PointT

Shubham Verma
fonte
7

A unionpalavra-chave, embora ainda seja usada no C ++ 03 1 , é principalmente um remanescente dos dias C. O problema mais evidente é que ele funciona apenas com o POD 1 .

A idéia da união, no entanto, ainda está presente, e de fato as bibliotecas Boost apresentam uma classe semelhante à união:

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

Qual tem a maioria dos benefícios do union(se não todos) e acrescenta:

  • capacidade de usar corretamente tipos não POD
  • segurança do tipo estático

Na prática, demonstrou-se que era equivalente a uma combinação de union+ enume comparou-se que era tão rápido (embora boost::anyseja mais do domínio de dynamic_cast, pois usa RTTI).

1 As uniões foram atualizadas no C ++ 11 ( uniões irrestritas ) e agora podem conter objetos com destruidores, embora o usuário precise invocar o destruidor manualmente (no membro da união atualmente ativo). Ainda é muito mais fácil usar variantes.

Matthieu M.
fonte
Isso não é mais verdade nas versões mais recentes do c ++. Veja a resposta de jrsala, por exemplo.
Andrew
@ Andrew: Atualizei a resposta para mencionar que o C ++ 11, com uniões irrestritas, permitia que tipos com destruidores fossem armazenados em união. Eu ainda mantenho minha posição de que você realmente está muito melhor usando uniões etiquetadas , boost::variantdo que tentando usar uniões por conta própria. Há comportamentos indefinidos demais ao redor dos sindicatos que suas chances de acertar são péssimas.
Matthieu M.
3

Do artigo da Wikipedia sobre sindicatos :

A principal utilidade de uma união é economizar espaço , pois fornece uma maneira de permitir que muitos tipos diferentes sejam armazenados no mesmo espaço. Os sindicatos também fornecem polimorfismo bruto . No entanto, não há verificação de tipos, portanto, cabe ao programador garantir que os campos apropriados sejam acessados ​​em diferentes contextos. O campo relevante de uma variável de união é normalmente determinado pelo estado de outras variáveis, possivelmente em uma estrutura anexa.

Um idioma comum de programação C usa uniões para executar o que o C ++ chama de reinterpret_cast, atribuindo a um campo de uma união e lendo de outro, como é feito no código que depende da representação bruta dos valores.

thkala
fonte
2

Nos primeiros dias de C (por exemplo, como documentado em 1974), todas as estruturas compartilhavam um espaço de nome comum para seus membros. Cada nome de membro foi associado a um tipo e um deslocamento; se "wd_woozle" fosse um "int" no deslocamento 12, então, dado um ponteiro pde qualquer tipo de estrutura, p->wd_woozleseria equivalente a *(int*)(((char*)p)+12). A linguagem exigia que todos os membros de todos os tipos de estruturas tivessem nomes exclusivos, exceto que permitia explicitamente a reutilização de nomes de membros nos casos em que todas as estruturas em que foram usadas as tratavam como uma sequência inicial comum.

O fato de os tipos de estrutura poderem ser utilizados com promiscuidade tornou possível que as estruturas se comportassem como se contivessem campos sobrepostos. Por exemplo, dadas definições:

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

O código pode declarar uma estrutura do tipo "float1" e, em seguida, usar "members" b0 ... b3 para acessar os bytes individuais nela. Quando o idioma era alterado para que cada estrutura recebesse um espaço para nome separado para seus membros, o código que dependia da capacidade de acessar as coisas de várias maneiras seria interrompido. Os valores da separação de namespaces para diferentes tipos de estrutura foram suficientes para exigir que esse código fosse alterado para acomodá-lo, mas o valor de tais técnicas foi suficiente para justificar a extensão do idioma para continuar a suportá-lo.

Código que tinha sido escrito para explorar a capacidade de acessar o armazenamento dentro de um struct float1como se fosse um struct byte4poderia ser feito para trabalhar no novo idioma, adicionando uma declaração: union f1b4 { struct float1 ff; struct byte4 bb; };, declarando objetos como tipo union f1b4;, em vez de struct float1, e substituindo acessos f0, b0, b1, etc . com ff.f0, bb.b0, bb.b1, etc. Enquanto existem melhores maneiras tal código poderia ter sido suportados, a unionabordagem era pelo menos um pouco viável, pelo menos, com interpretações C89 da era das regras aliasing.

supercat
fonte
1

Digamos que você tenha n tipos diferentes de configurações (apenas um conjunto de variáveis ​​que define parâmetros). Usando uma enumeração dos tipos de configuração, você pode definir uma estrutura que tenha o ID do tipo de configuração, juntamente com uma união de todos os diferentes tipos de configurações.

Dessa forma, onde quer que você passe a configuração, use o ID para determinar como interpretar os dados de configuração, mas se as configurações fossem enormes, você não seria forçado a ter estruturas paralelas para cada tipo potencial de perda de espaço.

Gavin H
fonte
1

Um recente impulso à importância já elevada da união foi dado pela Regra Estrita de Alias introduzida na versão recente do padrão C.

Você pode usar os sindicatos para fazer punções sem violar o padrão C.
Este programa tem um comportamento não especificado (porque eu assumi isso floate unsigned inttem o mesmo comprimento), mas não um comportamento indefinido (veja aqui ).

#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;
}
Comunidade
fonte
As regras de acesso por tipo não estão apenas nas versões "recentes" do Padrão. Toda versão do C inclui essencialmente as mesmas regras. O que mudou foi que os compiladores usados ​​para considerar a nota de rodapé "O objetivo desta lista é especificar as circunstâncias nas quais um objeto pode ou não ser aliasado". como indicando que a regra não se aplicava a casos que não envolviam aliasing como escrito , mas agora a tratam como um convite para reescrever código para criar aliasing onde não havia nenhum.
Supercat
1

Gostaria de acrescentar um bom exemplo prático para usar a calculadora / interpretadora de fórmula de implementação de união ou usar algum tipo disso na computação (por exemplo, você deseja usar modificável durante partes de tempo de execução de suas fórmulas computacionais - resolvendo a equação numericamente - apenas por exemplo). Portanto, convém definir números / constantes de diferentes tipos (número inteiro, ponto flutuante e até números complexos) como este:

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

Portanto, você está economizando memória e o que é mais importante - evita alocações dinâmicas para quantidades provavelmente extremas (se você usa muitos números definidos em tempo de execução) de objetos pequenos (em comparação com implementações por herança de classe / polimorfismo). Mas o mais interessante é que você ainda pode usar o poder do polimorfismo C ++ (se você é fã de despacho duplo, por exemplo;) com esse tipo de estrutura. Basta adicionar o ponteiro de interface "fictício" à classe pai de todos os tipos de números como um campo dessa estrutura, apontando para esta instância vez de / além do tipo bruto, ou use bons e antigos ponteiros de função C.

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);
 }
}

para que você possa usar o polimorfismo em vez das verificações de tipo com o switch (tipo) - com implementação eficiente em memória (sem alocação dinâmica de objetos pequenos) - se necessário, é claro.

Mastermind
fonte
Isso pode ser útil ao criar um idioma dinâmico. O problema que acho que resolverá é modificar uma variável de tipo desconhecido em massa sem implementar essa modificação N vezes. As macros são horríveis para isso e o modelo é praticamente impossível.
Andrew
0

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

Os usos da união são poucos e distantes entre si. Na maioria dos computadores, o tamanho de um ponteiro e um int são geralmente iguais - isso ocorre porque ambos geralmente se encaixam em um registro na CPU. Portanto, se você deseja fazer um lançamento rápido e sujo de um ponteiro para um int ou de outra maneira, declare uma união.

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

Outro uso de uma união está em um protocolo de comando ou mensagem em que mensagens de tamanhos diferentes são enviadas e recebidas. Cada tipo de mensagem conterá informações diferentes, mas cada uma terá uma parte fixa (provavelmente uma estrutura) e um bit de parte variável. É assim que você pode implementá-lo.

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

struct msgstring80 {cabeça da estrutura fixa; mensagem char [80]; }
struct msgint10 {cabeça de estrutura fixa; mensagem int [10]; } struct msgack {cabeça de estrutura fixa; int ok; } tipo de mensagem da união {
struct msgstring50 m50; struct msgstring80 m80; struct msgint10 i10; struct msgack ack; }

Na prática, embora os sindicatos tenham o mesmo tamanho, faz sentido enviar apenas os dados significativos e não o espaço desperdiçado. Um msgack tem apenas 16 bytes de tamanho, enquanto um msgstring80 tem 92 bytes. Portanto, quando uma variável de tipo de mensagem é inicializada, seu campo de tamanho é definido de acordo com o tipo. Isso pode ser usado por outras funções para transferir o número correto de bytes.

ανώνυμος
fonte
0

Os sindicatos fornecem uma maneira de manipular diferentes tipos de dados em uma única área de armazenamento sem incorporar nenhuma informação independente da máquina no programa. Eles são análogos aos registros variantes no pascal.

Como exemplo, como pode ser encontrado em um gerenciador de tabela de símbolos do compilador, suponha que uma constante possa ser um int, um float ou um ponteiro de caractere. O valor de uma constante específica deve ser armazenado em uma variável do tipo apropriado, mas é mais conveniente para o gerenciamento de tabelas se o valor ocupar a mesma quantidade de armazenamento e for armazenado no mesmo local, independentemente do seu tipo. Esse é o objetivo de uma união - uma única variável que pode legitimamente conter qualquer um de vários tipos. A sintaxe é baseada em estruturas:

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

A variável u será grande o suficiente para conter o maior dos três tipos; o tamanho específico depende da implementação. Qualquer um desses tipos pode ser atribuído a u e depois usado em expressões, desde que o uso seja consistente

Khushal
fonte