Risposte:
I sindacati vengono spesso utilizzati per convertire tra le rappresentazioni binarie di numeri interi e float:
union
{
int i;
float f;
} u;
// Convert floating-point bits to integer:
u.f = 3.14159f;
printf("As integer: %08x\n", u.i);
Anche se questo è un comportamento tecnicamente indefinito secondo lo standard C (dovresti solo leggere il campo che è stato scritto più di recente), agirà in un modo ben definito praticamente in qualsiasi compilatore.
I sindacati vengono talvolta utilizzati anche per implementare lo pseudo-polimorfismo in C, dando una struttura un tag che indica il tipo di oggetto che contiene e quindi unendo insieme i possibili tipi:
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;
}
}
Ciò consente alle dimensioni di struct S
essere solo 12 byte, anziché 28.
I sindacati sono particolarmente utili nella programmazione integrata o in situazioni in cui è necessario l'accesso diretto all'hardware / alla memoria. Ecco un esempio banale:
typedef union
{
struct {
unsigned char byte1;
unsigned char byte2;
unsigned char byte3;
unsigned char byte4;
} bytes;
unsigned int dword;
} HW_Register;
HW_Register reg;
Quindi è possibile accedere al registro come segue:
reg.dword = 0x12345678;
reg.bytes.byte3 = 4;
L'endianness (ordine dei byte) e l'architettura del processore sono ovviamente importanti.
Un'altra utile funzione è il modificatore di bit:
typedef union
{
struct {
unsigned char b1:1;
unsigned char b2:1;
unsigned char b3:1;
unsigned char b4:1;
unsigned char reserved:4;
} bits;
unsigned char byte;
} HW_RegisterB;
HW_RegisterB reg;
Con questo codice è possibile accedere direttamente a un singolo bit nel registro / indirizzo di memoria:
x = reg.bits.b2;
La programmazione del sistema a basso livello è un esempio ragionevole.
IIRC, ho usato i sindacati per scomporre i registri hardware nei bit dei componenti. Quindi, puoi accedere a un registro a 8 bit (com'era, nel giorno in cui l'ho fatto ;-) nei bit del componente.
(Dimentico la sintassi esatta ma ...) Questa struttura consentirebbe l'accesso a un registro di controllo come control_byte o tramite i singoli bit. Sarebbe importante assicurarsi che i bit siano mappati sui bit di registro corretti per una data endianness.
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;
L'ho visto in un paio di librerie in sostituzione dell'eredità orientata agli oggetti.
Per esempio
Connection
/ | \
Network USB VirtualConnection
Se vuoi che la "classe" di connessione sia una delle precedenti, puoi scrivere qualcosa del tipo:
struct Connection
{
int type;
union
{
struct Network network;
struct USB usb;
struct Virtual virtual;
}
};
Esempio di utilizzo in libinfinity: http://git.0x539.de/?p=infinote.git;a=blob;f=libinfinity/common/inf-session.c;h=3e887f0d63bd754c6b5ec232948027cbbf4d61fc;hb=HEAD#l74
I sindacati consentono ai membri di dati che si escludono a vicenda di condividere la stessa memoria. Questo è abbastanza importante quando la memoria è più scarsa, come nei sistemi embedded.
Nel seguente esempio:
union {
int a;
int b;
int c;
} myUnion;
Questa unione occuperà lo spazio di un singolo int, piuttosto che 3 valori int separati. Se l'utente imposta il valore di a , quindi imposta il valore di b , sovrascriverà il valore di a poiché entrambi condividono la stessa posizione di memoria.
Molti usi. Basta fare grep union /usr/include/*
o in directory simili. La maggior parte dei casi union
è racchiusa in un struct
e un membro della struttura indica a quale elemento nell'unione accedere. Ad esempio, checkout man elf
per implementazioni nella vita reale.
Questo è il principio base:
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;
}
Ecco un esempio di unione dalla mia base di codice (dalla memoria e parafrasata, quindi potrebbe non essere esatta). È stato usato per memorizzare elementi del linguaggio in un interprete che ho costruito. Ad esempio, il seguente codice:
set a to b times 7.
è costituito dai seguenti elementi del linguaggio:
Gli elementi del linguaggio sono stati definiti come ' #define
' valori quindi:
#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 la seguente struttura è stata utilizzata per memorizzare ogni elemento:
typedef struct {
int typ;
union {
char *str;
int val;
}
} tElem;
quindi la dimensione di ogni elemento era la dimensione dell'unione massima (4 byte per il tipo e 4 byte per l'unione, sebbene questi siano valori tipici, le dimensioni effettive dipendono dall'implementazione).
Per creare un elemento "set", dovresti usare:
tElem e;
e.typ = ELEM_SYM_SET;
Per creare un elemento "variabile [b]", devi usare:
tElem e;
e.typ = ELEM_VARIABLE;
e.str = strdup ("b"); // make sure you free this later
Per creare un elemento "costante [7]", dovresti usare:
tElem e;
e.typ = ELEM_CONSTANT;
e.val = 7;
e puoi facilmente espanderlo per includere floats ( float flt
) o rationals ( struct ratnl {int num; int denom;}
) e altri tipi.
La premessa di base è che i str
e val
non sono contigui nella memoria, in realtà si sovrappongono, quindi è un modo per ottenere una vista diversa sullo stesso blocco di memoria, illustrato qui, in cui la struttura si basa sulla posizione della memoria 0x1010
e numeri interi e puntatori sono entrambi 4 byte:
+-----------+
0x1010 | |
0x1011 | typ |
0x1012 | |
0x1013 | |
+-----+-----+
0x1014 | | |
0x1015 | str | val |
0x1016 | | |
0x1017 | | |
+-----+-----+
Se fosse solo in una struttura, sarebbe simile a questo:
+-------+
0x1010 | |
0x1011 | typ |
0x1012 | |
0x1013 | |
+-------+
0x1014 | |
0x1015 | str |
0x1016 | |
0x1017 | |
+-------+
0x1018 | |
0x1019 | val |
0x101A | |
0x101B | |
+-------+
make sure you free this later
commento deve essere rimosso dall'elemento costante?
Direi che è più semplice riutilizzare la memoria che potrebbe essere utilizzata in diversi modi, ad esempio risparmiando memoria. Ad esempio, ti piacerebbe fare qualche struttura "variante" in grado di salvare una stringa corta e un numero:
struct variant {
int type;
double number;
char *string;
};
In un sistema a 32 bit ciò comporterebbe l'utilizzo di almeno 96 bit o 12 byte per ciascuna istanza di variant
.
Utilizzando un'unione è possibile ridurre le dimensioni fino a 64 bit o 8 byte:
struct variant {
int type;
union {
double number;
char *string;
} value;
};
Puoi risparmiare ancora di più se desideri aggiungere più tipi di variabili diverse, ecc. Potrebbe essere vero che puoi fare cose simili lanciando un puntatore vuoto, ma l'unione lo rende molto più accessibile e digita sicuro. Tali risparmi non sembrano enormi, ma stai risparmiando un terzo della memoria utilizzata per tutte le istanze di questa struttura.
È difficile pensare a un'occasione specifica in cui avresti bisogno di questo tipo di struttura flessibile, forse in un protocollo di messaggio in cui invierai diverse dimensioni di messaggi, ma anche allora ci sono probabilmente alternative migliori e più programmabili.
I sindacati sono un po 'come i tipi di varianti in altre lingue: possono contenere solo una cosa alla volta, ma quella cosa potrebbe essere un int, un float ecc. A seconda di come la dichiarate.
Per esempio:
typedef union MyUnion MYUNION;
union MyUnion
{
int MyInt;
float MyFloat;
};
MyUnion conterrà solo un int o un float, a seconda dell'ultima impostazione impostata . Quindi facendo questo:
MYUNION u;
u.MyInt = 10;
ora hai un int uguale a 10;
u.MyFloat = 1.0;
ora hai un float pari a 1.0. Non contiene più un int. Ovviamente ora se provi a fare printf ("MyInt =% d", u.MyInt); allora probabilmente otterrai un errore, anche se non sono sicuro del comportamento specifico.
La dimensione dell'unione è dettata dalla dimensione del suo campo più grande, in questo caso il galleggiante.
sizeof(int) == sizeof(float)
( == 32
) di solito.
I sindacati vengono utilizzati quando si desidera modellare strutture definite da hardware, dispositivi o protocolli di rete o quando si crea un numero elevato di oggetti e si desidera risparmiare spazio. In realtà non ne hai bisogno il 95% delle volte, mantieni il codice di facile debug.
Molte di queste risposte riguardano il casting da un tipo all'altro. Ottengo il massimo utilizzo dai sindacati con gli stessi tipi, solo di più (ad esempio quando analizzo un flusso di dati seriale). Consentono che l'analisi / costruzione di un pacchetto incorniciato diventi banale.
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..
}
}
Modifica Il commento sull'endianness e il padding della struttura sono validi e grandi preoccupazioni. Ho usato questo codice quasi interamente nei software embedded, la maggior parte dei quali avevo il controllo di entrambe le estremità della pipe.
I sindacati sono fantastici. Un uso intelligente dei sindacati che ho visto è quello di usarli quando si definisce un evento. Ad esempio, potresti decidere che un evento è a 32 bit.
Ora, all'interno di quei 32 bit, potresti voler designare i primi 8 bit come per un identificatore del mittente dell'evento ... A volte hai a che fare con l'evento nel suo insieme, a volte lo sezioni e confronti i suoi componenti. i sindacati ti danno la flessibilità di fare entrambe le cose.
evento sindacale { eventCode lungo senza segno; unsigned char eventParts [4]; };
Che dire di VARIANT
ciò è usato nelle interfacce COM? Ha due campi: "tipo" e un'unione contenente un valore effettivo che viene trattato in base al campo "tipo".
Ho usato l'unione mentre stavo codificando per dispositivi embedded. Ho C int che è lungo 16 bit. E ho bisogno di recuperare gli 8 bit più alti e gli 8 bit più bassi quando devo leggere / memorizzare su EEPROM. Quindi ho usato in questo modo:
union data {
int data;
struct {
unsigned char higher;
unsigned char lower;
} parts;
};
Non richiede spostamento, quindi il codice è più facile da leggere.
D'altra parte, ho visto un vecchio codice stl C ++ che utilizzava unione per allocatore stl. Se sei interessato, puoi leggere il codice sorgente sgi stl . Eccone un pezzo:
union _Obj {
union _Obj* _M_free_list_link;
char _M_client_data[1]; /* The client sees this. */
};
struct
attorno al tuo higher
/ lower
? In questo momento entrambi dovrebbero puntare solo al primo byte.
Dai un'occhiata a questo: Gestione dei comandi del buffer X.25
Uno dei tanti possibili comandi X.25 viene ricevuto in un buffer e gestito sul posto utilizzando un'UNIONE di tutte le possibili strutture.
Nelle prime versioni di C, tutte le dichiarazioni di struttura condividevano un insieme comune di campi. Dato:
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];};
un compilatore produrrebbe essenzialmente una tabella delle dimensioni delle strutture (e possibilmente allineamenti) e una tabella separata dei nomi, dei tipi e degli offset dei membri delle strutture. Il compilatore non ha tenuto traccia di quali membri appartenessero a quali strutture e consentirebbe a due strutture di avere un membro con lo stesso nome solo se il tipo e l'offset corrispondessero (come con il membro q
di struct x
estruct y
). Se p fosse un puntatore a qualsiasi tipo di struttura, p-> q aggiungerebbe l'offset di "q" al puntatore p e recupererebbe un "int" dall'indirizzo risultante.
Considerata la semantica di cui sopra, è stato possibile scrivere una funzione in grado di eseguire intercambiabili alcune utili operazioni su più tipi di struttura, a condizione che tutti i campi utilizzati dalla funzione siano allineati con campi utili all'interno delle strutture in questione. Questa era una caratteristica utile e cambiare C per convalidare i membri utilizzati per l'accesso alla struttura rispetto ai tipi delle strutture in questione avrebbe significato perderlo in assenza di un mezzo per avere una struttura che può contenere più campi con nome allo stesso indirizzo. L'aggiunta di tipi di "unione" a C ha contribuito a colmare un po 'il divario (anche se non, IMHO, così come avrebbe dovuto essere).
Una parte essenziale della capacità dei sindacati di colmare questa lacuna era il fatto che un puntatore a un membro sindacale poteva essere convertito in un puntatore a qualsiasi unione contenente quel membro e un puntatore a qualsiasi unione poteva essere convertito in un puntatore a qualsiasi membro. Mentre lo standard C89 non ha espressamente affermato che lanciare un T*
direttamente su un U*
equivale a lanciarlo su un puntatore a qualsiasi tipo di unione contenente entrambi T
e U
, quindi, lanciare quello su U*
, nessun comportamento definito di quest'ultima sequenza di cast sarebbe influenzato dal tipo di unione utilizzato e lo Standard non specificava alcuna semantica contraria per un cast diretto da T
a U
. Inoltre, nei casi in cui una funzione ha ricevuto un puntatore di origine sconosciuta, il comportamento della scrittura di un oggetto tramite la T*
conversione diT*
a a U*
, e quindi leggere l'oggetto tramite U*
equivarrebbe a scrivere un'unione tramite membro di tipo T
e leggere come tipo U
, che sarebbe definito in alcuni casi standard (ad esempio quando si accede a membri di sequenza iniziale comune) e definito dall'implementazione (piuttosto di Undefined) per il resto. Mentre era raro che i programmi sfruttassero le garanzie CIS con oggetti reali di tipo sindacale, era molto più comune sfruttare il fatto che i puntatori a oggetti di origine sconosciuta dovevano comportarsi come puntatori ai membri del sindacato e avere le garanzie comportamentali ad essi associate.
foo
è un int
offset 8, anyPointer->foo = 1234;
significa "prendi l'indirizzo in qualunque puntatore, spostalo di 8 byte ed esegui un archivio intero del valore 1234 all'indirizzo risultante. Il compilatore non avrebbe bisogno di sapere o preoccuparsi se anyPointer
identificato qualsiasi tipo di struttura che fosse foo
elencata tra i suoi membri
anyPointer
identifica con un membro struct, come farà il compilatore a controllare queste condizioni to have a member with the same name only if the type and offset matched
del tuo post?
p->foo
dipenderà dal tipo e dall'offset di foo
. Essenzialmente, p->foo
era una scorciatoia per *(typeOfFoo*)((unsigned char*)p + offsetOfFoo)
. Per quanto riguarda la tua ultima domanda, quando un compilatore incontra una definizione di membro struct, richiede che non esista alcun membro con quel nome o che il membro con quel nome abbia lo stesso tipo e offset; Immagino che si sarebbe sballato se esistesse una definizione di membro strutt non corrispondente, ma non so come gestisse gli errori.
Un esempio semplice e molto utile è ....
Immaginare:
hai un uint32_t array[2]
e vuoi accedere al 3o e 4o byte della catena di byte. potresti fare *((uint16_t*) &array[1])
. Ma questo purtroppo infrange le rigide regole di aliasing!
Ma i compilatori noti ti consentono di fare quanto segue:
union un
{
uint16_t array16[4];
uint32_t array32[2];
}
tecnicamente questa è ancora una violazione delle regole. ma tutti gli standard noti supportano questo utilizzo.