Quando qualcuno userebbe un sindacato? È un residuo dei soli giorni in C?


133

Ho imparato ma non ho davvero i sindacati. Ogni testo C o C ++ che passo attraverso li introduce (a volte di sfuggita), ma tendono a dare pochissimi esempi pratici sul perché o dove usarli. Quando i sindacati sarebbero utili in un caso moderno (o addirittura ereditato)? Le mie uniche due ipotesi sarebbero la programmazione di microprocessori quando hai uno spazio molto limitato con cui lavorare, o quando stai sviluppando un'API (o qualcosa di simile) e vuoi forzare l'utente finale ad avere solo un'istanza di più oggetti / tipi in Una volta. Queste due ipotesi sono persino vicine alla destra?


31
C / C ++ non è un linguaggio. I sindacati sono moderatamente utili in C e ampiamente inutili in C ++. Sarebbe corretto dire che in C ++ sono un "residuo del C ++ basato su C", ma non dire che sono "un residuo dei soli giorni C" come se C ++ superasse C.
R .. GitHub Smetti di aiutare ICE il

12
Puoi approfondire qual è il sostituto del c ++ per i sindacati o perché sono inutili in c ++?
Russel,

3
Il sostituto del C ++ per i sindacati sono le classi e l'eredità - i sindacati in C sono usati quasi esclusivamente per il polimorfismo sicuro. Qualcosa di classe è molto meglio. (Vedi la risposta di vz0 per il polimorfismo di tipo C)
tobyodavies

6
@R ..: i sindacati sono ancora moderatamente utili in C ++. Vedi le risposte sotto.
Michael,

2
I sindacati possono essere straordinariamente preziosi nelle viscere di un sistema operativo o, ad esempio, in un pacchetto che assembla / disassembla file audio. In tali contesti vengono utilizzati molteplici modi: conversione dati / endian, polimorfismo di basso livello, et al. Sì, ci sono altre soluzioni allo stesso problema (principalmente il cast tra tipi di puntatori), ma i sindacati sono spesso auto-documentati più puliti e migliori.
Hot Licks

Risposte:


105

I sindacati vengono generalmente utilizzati con la compagnia di un discriminatore: una variabile che indica quale dei campi del sindacato è valido. Ad esempio, supponiamo che tu voglia creare il tuo tipo di 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;
    };
};

Quindi lo useresti come:

/* 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;
    ...
    }
}

Questo è in realtà un linguaggio piuttosto comune, specialmente sugli interni di Visual Basic.

Per un esempio reale, vedere l' unione SDL_Event di SDL . ( codice sorgente effettivo qui ). C'è un typecampo nella parte superiore del sindacato e lo stesso campo viene ripetuto su ogni struttura di evento SDL_ *. Quindi, per gestire l'evento corretto è necessario verificare il valore del typecampo.

I vantaggi sono semplici: esiste un solo tipo di dati per gestire tutti i tipi di eventi senza utilizzare memoria non necessaria.


2
Grande! In tal caso, ora mi chiedo perché la funzione Sdl non sia stata solo implementata come gerarchia di classi. È quello per renderlo C compatibile e non solo C ++?
Russel,

12
Le classi C ++ di @Russel non possono essere utilizzate da un programma C, ma le strutture / i sindacati C possono essere facilmente accessibili da C ++ usando un blocco "C" esterno.
vz0

1
Questo modello di variante viene spesso utilizzato anche per gli interpreti del linguaggio di programmazione, ad esempio la definizione di struct objectin github.com/petermichaux/bootstrap-scheme/blob/v0.21/scheme.c
Adam Rosenfield,

1
Spiegazione eccezionale. Ho sempre saputo quali fossero i sindacati, ma non ho mai visto una ragione reale del perché qualcuno sarebbe abbastanza pazzo da usarli :) Grazie per l'esempio.
riwalk

@ Stargazer712, ricerca di codici di Google: google.com/…
kagali-san

87

Trovo i sindacati C ++ piuttosto interessanti. Sembra che le persone di solito pensino solo al caso d'uso in cui si vuole cambiare il valore di un'istanza sindacale "sul posto" (che, a quanto pare, serve solo per risparmiare memoria o eseguire conversioni dubbie).

In effetti, i sindacati possono essere di grande potenza come strumento di ingegneria del software, anche quando non si cambia mai il valore di un'istanza sindacale .

Caso d'uso 1: il camaleonte

Con i sindacati, è possibile raggruppare un numero di classi arbitrarie in un'unica denominazione, che non è privo di somiglianze con il caso di una classe base e delle sue classi derivate. Ciò che cambia, tuttavia, è ciò che puoi e non puoi fare con una determinata istanza sindacale:

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

Sembra che il programmatore debba essere certo del tipo di contenuto di una determinata istanza sindacale quando vuole usarlo. È il caso nella funzione fsopra. Tuttavia, se una funzione dovesse ricevere un'istanza di sindacato come argomento passato, come nel caso gprecedente, non saprebbe cosa farne. Lo stesso vale per le funzioni che restituiscono un'istanza di unione, vedi h: come fa il chiamante a sapere cosa c'è dentro?

Se un'istanza sindacale non viene mai passata come argomento o come valore di ritorno, allora è destinata ad avere una vita molto monotona, con punte di eccitazione quando il programmatore sceglie di cambiarne il contenuto:

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

E questo è il caso d'uso (non) più popolare dei sindacati. Un altro caso d'uso è quando un'istanza di unione arriva con qualcosa che ti dice il suo tipo.

Caso d'uso 2: "Piacere di conoscerti, io sono objectdi Class"

Supponiamo che un programmatore scelto per associare sempre un'istanza sindacale a un descrittore di tipo (lascerò a discrezione del lettore l'immaginazione di un'implementazione per uno di questi oggetti). Ciò vanifica lo scopo dell'unione stessa se ciò che il programmatore desidera è risparmiare memoria e che la dimensione del descrittore del tipo non è trascurabile rispetto a quella dell'unione. Ma supponiamo che sia cruciale che l'istanza sindacale possa essere passata come argomento o come valore di ritorno con il chiamato o il chiamante che non sa cosa c'è dentro.

Quindi il programmatore deve scrivere switchun'istruzione di flusso di controllo per distinguere Bruce Wayne da un bastoncino di legno o qualcosa di equivalente. Non è male quando ci sono solo due tipi di contenuti nell'unione ma ovviamente l'unione non si ridimensiona più.

Caso d'uso 3:

Come gli autori di una raccomandazione per lo standard ISO C ++ lo hanno riportato nel 2008,

Molti importanti domini problematici richiedono un numero elevato di oggetti o risorse di memoria limitate. In queste situazioni la conservazione dello spazio è molto importante e un'unione è spesso un modo perfetto per farlo. In effetti, un caso d'uso comune è la situazione in cui un sindacato non cambia mai il suo membro attivo durante la sua vita. Può essere costruito, copiato e distrutto come se fosse una struttura contenente un solo membro. Un'applicazione tipica di ciò sarebbe quella di creare una raccolta eterogenea di tipi non correlati che non sono allocati dinamicamente (forse sono costruiti sul posto in una mappa o membri di un array).

E ora, un esempio, con un diagramma di classe UML:

molte composizioni per la classe A

La situazione in un inglese semplice: un oggetto di classe A può avere oggetti di qualsiasi classe tra B1, ..., Bn e al massimo uno di ogni tipo, con n un numero piuttosto grande, diciamo almeno 10.

Non vogliamo aggiungere campi (membri dei dati) ad A in questo modo:

private:
    B1 b1;
    .
    .
    .
    Bn bn;

perché n potrebbe variare (potremmo voler aggiungere classi Bx al mix) e perché ciò causerebbe un disastro con i costruttori e perché gli oggetti A occuperebbero molto spazio.

Potremmo usare un contenitore stravagante di void*puntatori agli Bxoggetti con cast per recuperarli, ma è fugace e quindi in stile C ... ma, cosa più importante, che ci lascerebbe con la vita di molti oggetti allocati dinamicamente da gestire.

Invece, ciò che può essere fatto è questo:

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

Quindi, per ottenere il contenuto di un'istanza di unione data, usi a.get(TYPE_B2).b2e simili, dove si atrova Aun'istanza di classe .

Ciò è tanto più potente in quanto i sindacati non hanno restrizioni in C ++ 11. Vedi il documento collegato sopra o questo articolo per i dettagli.


Ciò è stato molto utile e la serie del secondo articolo è stata molto istruttiva. Grazie.
Andrew,

38

Un esempio è nel regno incorporato, in cui ogni bit di un registro può significare qualcosa di diverso. Ad esempio, un'unione di un numero intero a 8 bit e una struttura con 8 campi bit a 1 bit separati consente di modificare un bit o l'intero byte.


7
Questo è molto comune anche nei driver di dispositivo. Qualche anno fa ho scritto molto codice usando sindacati come questo per un progetto. Normalmente non è raccomandato e in alcuni casi può essere specifico del compilatore, ma funziona.
thkala,

11
Non lo definirei "non raccomandato". Nello spazio incorporato è spesso molto più pulito e meno soggetto a errori rispetto alle alternative, che di solito comportano molti cast espliciti void*o maschere e turni.
BTA

eh? Molti cast espliciti? Mi sembra semplici dichiarazioni come REG |= MASKe REG &= ~MASK. Se questo è soggetto a errori, inseriscili in a #define SETBITS(reg, mask)e #define CLRBITS(reg, mask). Non fare affidamento sul compilatore per ottenere i bit in un determinato ordine ( stackoverflow.com/questions/1490092/… )
Michael

26

Herb Sutter ha scritto in GOTW circa sei anni fa, con l' enfasi aggiunta:

"Ma non pensare che i sindacati siano solo una riserva dai tempi precedenti. I sindacati sono forse più utili per risparmiare spazio consentendo la sovrapposizione dei dati, e questo è ancora desiderabile nel C ++ e nel mondo moderno di oggi. Ad esempio, alcuni dei più C ++ avanzatole implementazioni di librerie standard nel mondo ora usano proprio questa tecnica per implementare la "piccola ottimizzazione delle stringhe", un'ottima alternativa di ottimizzazione che riutilizza la memoria all'interno di un oggetto stringa stesso: per stringhe di grandi dimensioni, lo spazio all'interno dell'oggetto stringa memorizza il solito puntatore dinamicamente buffer allocato e informazioni di pulizia come la dimensione del buffer; per stringhe di piccole dimensioni, viene invece riutilizzato lo stesso spazio per archiviare direttamente il contenuto della stringa ed evitare completamente qualsiasi allocazione dinamica della memoria. Per ulteriori informazioni sull'ottimizzazione delle stringhe di piccole dimensioni (e altre ottimizzazioni e pessimizzazioni delle stringhe in profondità considerevole), vedere ... ".

E per un esempio meno utile, vedi la lunga ma inconcludente domanda gcc, alias rigoroso e ricerca di un sindacato .


23

Bene, un esempio d'uso che mi viene in mente è questo:

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

È quindi possibile accedere alle parti separate a 8 bit di quel blocco di dati a 32 bit; tuttavia, preparati a essere potenzialmente morso dall'endianità.

Questo è solo un esempio ipotetico, ma ogni volta che si desidera dividere i dati in un campo in parti componenti come questa, è possibile utilizzare un'unione.

Detto questo, esiste anche un metodo che è endian-safe:

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

Ad esempio, poiché tale operazione binaria verrà convertita dal compilatore nell'endianness corretto.


Penso che la domanda sia meglio presa come quando si dovrebbero usare i sindacati. Hai fornito una risposta su dove un sindacato non è lo strumento corretto, che penso dovrebbe essere più chiaro in questa risposta.
Michael,

15

Alcuni usi per i sindacati:

  • Fornire un'interfaccia di endianness generale a un host esterno sconosciuto.
  • Manipola i dati in virgola mobile dell'architettura CPU esterna, ad esempio accettando VAX G_FLOATS da un collegamento di rete e convertendoli in reals lunghi IEEE 754 per l'elaborazione.
  • Fornisci un accesso diretto a un bit di livello superiore.
union {
      unsigned char   byte_v[16];
      long double     ld_v;
 }

Con questa dichiarazione, è semplice visualizzare i valori di byte esadecimali di a long double, modificare il segno dell'esponente, determinare se si tratta di un valore denormale o implementare una doppia aritmetica lunga per una CPU che non lo supporta, ecc.

  • Risparmio di spazio di archiviazione quando i campi dipendono da determinati valori:

    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 i file include da utilizzare con il compilatore. Troverai decine di centinaia di usi di 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 {

5
Tsk tsk! Due voti negativi e nessuna spiegazione. Questo è deludente.
Wallyk,

L'esempio con una persona che può contenere un uomo e una donna è un pessimo design ai miei occhi. Perché non una classe base di persona e una di derivazione uomo e donna? Siamo spiacenti, ma cercare manualmente una variabile per determinare il tipo memorizzato in un campo dati non è affatto una buona idea. Questo è un codice c fatto a mano mai visto per anni. Ma nessun voto negativo, è solo il mio punto di vista :-)
Klaus

4
Immagino tu abbia ottenuto i voti negativi per l'unione "castrata" o "gravidanze". È un po 'malato.
Akaltar,

2
Sì, immagino sia stata una giornata buia.
Wallyk,

14

I sindacati sono utili quando si tratta di dati a livello di byte (basso livello).

Uno dei miei recenti usi riguardava la modellazione di indirizzi IP che appare come di seguito:

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

7
Tieni presente, tuttavia, che l'accesso a materie prime come quelle non è standard e potrebbe non funzionare come previsto con tutti i compilatori.
nn.

3
Inoltre, è molto comune vederlo usato in un modo che non garantisce l'allineamento, che è un comportamento indefinito.
Mooing Duck

10

Un esempio quando ho usato un sindacato:

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

        };
...
}

questo mi permette di accedere ai miei dati come un array o gli elementi.

Ho usato un'unione per far sì che termini diversi puntino allo stesso valore. Nell'elaborazione delle immagini, sia che stavo lavorando su colonne o larghezza o sulle dimensioni nella direzione X, può diventare confuso. Per ovviare a questo problema, uso un sindacato, quindi so quali descrizioni vanno insieme.

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

12
Si noti che sebbene questa soluzione funzioni sulla maggior parte delle piattaforme osservabili, l'impostazione dei valori su _x, _y, _z e l'accesso a _coord è un comportamento indefinito. Lo scopo principale dei sindacati è la conservazione dello spazio. È necessario accedere esattamente allo stesso elemento di unione precedentemente impostato.
anxieux,

1
questo è anche il modo in cui lo uso, inoltre uso uno std :: array forr coords e alcuni static_asserts
Viktor Sehr

1
Questo codice viola le rigide regole di aliasing e non deve essere raccomandato.
Walter,

Esiste forse un modo per migliorare il sindacato in modo che sia affidabile farlo?
Andrew,

8

I sindacati forniscono polimorfismo in C.


18
Ho pensato void*che ^^

2
@ user166390 Il polimorfismo utilizza la stessa interfaccia per manipolare più tipi; void * non ha interfaccia.
Alice

2
In C, il polimorfismo è comunemente implementato attraverso tipi opachi e / o puntatori a funzioni. Non ho idea di come o perché useresti un sindacato per raggiungere questo obiettivo. Sembra una cattiva idea.
Lundin,

7

Un brillante utilizzo dell'unione è l'allineamento della memoria, che ho trovato nel codice sorgente PCL (Point Cloud Library). La singola struttura di dati nell'API può indirizzare due architetture: CPU con supporto SSE e CPU senza supporto SSE. Ad esempio: la struttura dei dati per PointXYZ è

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

I 3 galleggianti sono imbottiti con un galleggiante aggiuntivo per l'allineamento SSE. Così per

PointXYZ point;

L'utente può accedere a point.data [0] o point.x (a seconda del supporto SSE) per accedere ad esempio alla coordinata x. Ulteriori dettagli di utilizzo più simili sono disponibili sul seguente link: Documentazione PCL Tipi PointT


7

La unionparola chiave, sebbene sia ancora utilizzata in C ++ 03 1 , è principalmente un residuo dei giorni C. Il problema più evidente è che funziona solo con POD 1 .

L'idea dell'unione, tuttavia, è ancora presente, e in effetti le librerie Boost presentano una classe simile a un'unione:

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

Che ha la maggior parte dei vantaggi di union(se non tutti) e aggiunge:

  • capacità di utilizzare correttamente tipi non POD
  • sicurezza di tipo statico

In pratica, è stato dimostrato che era equivalente a una combinazione di union+ enum, e ha messo a confronto che era il più veloce (mentre boost::anyè più del regno di dynamic_cast, poiché utilizza RTTI).

1 I sindacati sono stati aggiornati in C ++ 11 ( sindacati senza restrizioni ) e ora possono contenere oggetti con distruttori, sebbene l'utente debba invocare il distruttore manualmente (sul membro sindacale attualmente attivo). È ancora molto più facile usare le varianti.


Questo non è più vero nelle versioni più recenti di c ++. Vedi la risposta di jrsala, per esempio.
Andrew,

@Andrew: ho aggiornato la risposta per menzionare che C ++ 11, con i sindacati senza restrizioni, ha permesso di archiviare i tipi con distruttori in unione. Sono ancora convinto che tu stia davvero molto meglio usando i sindacati taggati , boost::variantpiuttosto che provare a usare i sindacati da soli. C'è un comportamento troppo indefinito che circonda i sindacati e che le tue possibilità di farlo bene sono spaventose.
Matthieu M.

3

Dalla articolo di Wikipedia sulle unioni :

L'utilità primaria di un'unione è quella di conservare lo spazio , poiché fornisce un modo per lasciare che molti tipi diversi siano memorizzati nello stesso spazio. I sindacati forniscono anche un polimorfismo grezzo . Tuttavia, non vi è alcun controllo dei tipi, quindi spetta al programmatore assicurarsi che i campi corretti siano accessibili in contesti diversi. Il campo rilevante di una variabile di unione è in genere determinato dallo stato di altre variabili, possibilmente in una struttura chiusa.

Un linguaggio di programmazione C comune utilizza i sindacati per eseguire ciò che C ++ chiama reinterpret_cast, assegnando a un campo di un sindacato e leggendo da un altro, come avviene nel codice che dipende dalla rappresentazione grezza dei valori.


2

Nei primi giorni di C (ad esempio come documentato nel 1974), tutte le strutture condividevano uno spazio dei nomi comune per i loro membri. Il nome di ciascun membro era associato a un tipo e un offset; se "wd_woozle" fosse un "int" all'offset 12, quindi dato un puntatore pdi qualsiasi tipo di struttura, p->wd_woozlesarebbe equivalente a *(int*)(((char*)p)+12). Il linguaggio richiedeva che tutti i membri di tutti i tipi di strutture avessero nomi univoci, tranne per il fatto che consentiva esplicitamente il riutilizzo dei nomi dei membri nei casi in cui ogni struttura in cui venivano utilizzati li trattava come una sequenza iniziale comune.

Il fatto che i tipi di struttura possano essere usati in modo promiscuo ha reso possibile che le strutture si comportassero come se contenessero campi sovrapposti. Ad esempio, date definizioni:

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

codice potrebbe dichiarare una struttura di tipo "float1" e quindi utilizzare "membri" b0 ... b3 per accedere ai singoli byte al suo interno. Quando la lingua veniva cambiata in modo tale che ogni struttura ricevesse uno spazio dei nomi separato per i suoi membri, il codice che si basava sulla capacità di accedere alle cose in diversi modi si spezzava. I valori di separazione degli spazi dei nomi per diversi tipi di strutture erano sufficienti per richiedere che tale codice venisse modificato per adattarlo, ma il valore di tali tecniche era sufficiente per giustificare l'estensione del linguaggio per continuare a supportarlo.

Codice che era stato scritto per sfruttare la possibilità di accedere alla memoria all'interno di una struct float1, come se si trattasse di una struct byte4potrebbe essere fatto al lavoro nella nuova lingua con l'aggiunta di una dichiarazione: union f1b4 { struct float1 ff; struct byte4 bb; };, dichiarando oggetti come tipo union f1b4;, piuttosto che struct float1, e la sostituzione di accessi a f0, b0, b1, ecc . con ff.f0, bb.b0, bb.b1, ecc Mentre ci sono modi migliori tale codice avrebbe potuto essere supportati, l' unionapproccio è stato almeno un po 'praticabile, almeno con le interpretazioni C89-era delle regole di aliasing.


1

Diciamo che hai n diversi tipi di configurazioni (essendo solo un insieme di variabili che definiscono i parametri). Utilizzando un'enumerazione dei tipi di configurazione, è possibile definire una struttura che ha l'ID del tipo di configurazione, insieme a un'unione di tutti i diversi tipi di configurazioni.

In questo modo, ovunque si passi la configurazione, è possibile utilizzare l'ID per determinare come interpretare i dati di configurazione, ma se le configurazioni fossero enormi non si sarebbe costretti ad avere strutture parallele per ogni potenziale tipo di spreco di spazio.


1

Una recente spinta sull'importanza, già elevata, dei sindacati è stata data dalla Regola Aliasing Rule introdotta nella recente versione dello standard C.

È possibile utilizzare i sindacati fare per digitare i punti senza violare lo standard C.
Questo programma ha un comportamento non specificato (perché l'ho assunto floate unsigned intha la stessa lunghezza) ma non un comportamento indefinito (vedi qui ).

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

Le regole di accesso al tipo non sono solo nelle versioni "recenti" dello standard. Ogni versione della C ha incluso essenzialmente le stesse regole. Ciò che è cambiato è che i compilatori hanno usato per considerare la nota a piè di pagina "L'intento di questo elenco è quello di specificare le circostanze in cui un oggetto può essere o meno aliasato". come indicando che la regola non doveva essere applicata nei casi che non comportavano l'aliasing come scritto , ma ora lo trattano come un invito a riscrivere il codice per creare un aliasing dove non ce n'erano stati.
supercat

1

Vorrei aggiungere un buon esempio pratico per l'utilizzo del sindacato: implementazione del calcolatore / interprete di formule o utilizzo di un qualche tipo di esso nel calcolo (ad esempio, si desidera utilizzare modificabile durante le parti runtime delle formule di calcolo - risolvere equazione numericamente - solo per esempio). Quindi potresti voler definire numeri / costanti di diversi tipi (numeri interi, in virgola mobile, anche complessi) in questo modo:

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

Quindi stai risparmiando memoria e ciò che è più importante - eviti allocazioni dinamiche per quantità probabilmente estreme (se usi molti numeri definiti in fase di esecuzione) di piccoli oggetti (rispetto alle implementazioni tramite ereditarietà / polimorfismo di classe). Ma ciò che è più interessante, puoi ancora usare la potenza del polimorfismo C ++ (se sei un fan del doppio dispacciamento, per esempio;) con questo tipo di struttura. Basta aggiungere il puntatore di interfaccia "fittizio" alla classe genitore di tutti i tipi di numeri come campo di questa struttura, puntando a questa istanza anziché / in aggiunta al tipo non elaborato, oppure utilizzare buoni vecchi puntatori di funzione 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);
 }
}

quindi puoi usare il polimorfismo invece dei controlli di tipo con switch (tipo) - con implementazione efficiente in termini di memoria (nessuna allocazione dinamica di piccoli oggetti) - se ne hai bisogno, ovviamente.


Questo può essere utile quando si crea un linguaggio dinamico. Il problema che penso risolverà sta modificando una variabile di tipo sconosciuto in massa senza implementare quella modifica N volte. Le macro sono orrende per questo e il templating è praticamente impossibile.
Andrew,

0

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

Gli usi dell'unione sono pochi e lontani tra loro. Sulla maggior parte dei computer, le dimensioni di un puntatore e di un int sono generalmente le stesse, questo perché entrambi si adattano di solito a un registro nella CPU. Quindi, se si desidera eseguire un cast rapido e sporco di un puntatore a un int o viceversa, dichiarare un'unione.

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

Un altro uso di un'unione è in un protocollo di comando o messaggio in cui vengono inviati e ricevuti messaggi di dimensioni diverse. Ogni tipo di messaggio conterrà informazioni diverse ma ognuna avrà una parte fissa (probabilmente una struttura) e un bit di parte variabile. Ecco come è possibile implementarlo ..

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

struct msgstring80 {struct head fixed; messaggio char [80]; }
struct msgint10 {struct head fixed; messaggio int [10]; } struct msgack {struct head fixed; int ok; } union messagetype {
struct msgstring50 m50; struct msgstring80 m80; struct msgint10 i10; struct msgack ack; }

In pratica, sebbene i sindacati abbiano le stesse dimensioni, ha senso inviare solo dati significativi e non spreco di spazio. Un msgack ha una dimensione di soli 16 byte mentre un msgstring80 ha 92 byte. Pertanto, quando una variabile messagetype viene inizializzata, il suo campo dimensioni viene impostato in base al tipo. Questo può quindi essere utilizzato da altre funzioni per trasferire il numero corretto di byte.


0

I sindacati forniscono un modo per manipolare diversi tipi di dati in una singola area di archiviazione senza incorporare alcuna informazione indipendente dalla macchina nel programma. Sono analoghi ai record delle varianti in pascal

Ad esempio, come potrebbe essere trovato in un gestore di tabelle dei simboli del compilatore, supponiamo che una costante possa essere un puntatore int, un float o un carattere. Il valore di una costante costante deve essere memorizzato in una variabile del tipo appropriato, tuttavia è più conveniente per la gestione delle tabelle se il valore occupa la stessa quantità di memoria ed è archiviato nello stesso posto indipendentemente dal suo tipo. Questo è lo scopo di un'unione - una singola variabile che può legittimamente contenere uno di diversi tipi. La sintassi si basa su strutture:

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

La variabile u sarà abbastanza grande da contenere il più grande dei tre tipi; la dimensione specifica dipende dall'implementazione. Ognuno di questi tipi può essere assegnato a te e quindi utilizzato nelle espressioni, purché l'uso sia coerente

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.