Quand faut-il recourir aux syndicats ? Pourquoi en avons-nous besoin ?
Réponses
Trop de publicités?Beaucoup d'usages. Il suffit de faire grep union /usr/include/*
ou dans des répertoires similaires. Dans la plupart des cas, le union
est enveloppé dans un struct
et un membre de la structure indique à quel élément de l'union il faut accéder. Par exemple, la caisse man elf
pour les mises en œuvre dans la vie réelle.
C'est le principe de 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;
}
Voici un exemple d'union tiré de ma propre base de code (de mémoire et paraphrasé, il se peut que ce ne soit pas exact). Elle était utilisée pour stocker des éléments de langage dans un interpréteur que j'ai construit. Par exemple, le code suivant :
set a to b times 7.
se compose des éléments linguistiques suivants :
- symbole [ensemble]
- variable[a]
- symbole [à]
- variable[b]
- symbole [fois]
- constante [7]
- symbole [.]
Les éléments linguistiques ont été définis comme #define
Ainsi, les valeurs de l'Union européenne :
#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
et la structure suivante a été utilisée pour stocker chaque élément :
typedef struct {
int typ;
union {
char *str;
int val;
}
} tElem;
alors la taille de chaque élément était la taille de l'union maximale (4 octets pour le type et 4 octets pour l'union, bien qu'il s'agisse de valeurs typiques, l'option réel dépendent de l'implémentation).
Pour créer un élément "set", il faut utiliser :
tElem e;
e.typ = ELEM_SYM_SET;
Pour créer un élément "variable[b]", il faut utiliser :
tElem e;
e.typ = ELEM_VARIABLE;
e.str = strdup ("b"); // make sure you free this later
Pour créer un élément "constant[7]", il faut utiliser :
tElem e;
e.typ = ELEM_CONSTANT;
e.val = 7;
et vous pourriez facilement l'étendre pour inclure les flottants ( float flt
) ou des rationnels ( struct ratnl {int num; int denom;}
) et d'autres types.
L'hypothèse de base est que le str
et val
ne sont pas contigus en mémoire, ils se chevauchent en fait, c'est donc un moyen d'obtenir une vue différente sur le même bloc de mémoire, illustré ici, où la structure est basée à l'emplacement mémoire 0x1010
et les entiers et les pointeurs sont tous deux de 4 octets :
+-----------+
0x1010 | |
0x1011 | typ |
0x1012 | |
0x1013 | |
+-----+-----+
0x1014 | | |
0x1015 | str | val |
0x1016 | | |
0x1017 | | |
+-----+-----+
Si c'était juste dans une structure, ça ressemblerait à ça :
+-------+
0x1010 | |
0x1011 | typ |
0x1012 | |
0x1013 | |
+-------+
0x1014 | |
0x1015 | str |
0x1016 | |
0x1017 | |
+-------+
0x1018 | |
0x1019 | val |
0x101A | |
0x101B | |
+-------+
Je dirais que cela facilite la réutilisation de la mémoire qui peut être utilisée de différentes manières, c'est-à-dire l'économie de mémoire. Par exemple, vous aimeriez créer une structure "variante" capable d'enregistrer une courte chaîne de caractères ainsi qu'un nombre :
struct variant {
int type;
double number;
char *string;
};
Dans un système à 32 bits, il en résulterait qu'au moins 96 bits ou 12 octets seraient utilisés pour chaque instance de variant
.
En utilisant une union, vous pouvez réduire la taille à 64 bits ou 8 octets :
struct variant {
int type;
union {
double number;
char *string;
} value;
};
Vous pouvez économiser encore plus si vous souhaitez ajouter d'autres types de variables, etc. Il est peut-être vrai que vous pouvez faire des choses similaires en coulant un pointeur vide, mais l'union le rend beaucoup plus accessible et plus sûr. Ces économies ne semblent pas énormes, mais vous économisez un tiers de la mémoire utilisée pour toutes les instances de cette structure.
Beaucoup de ces réponses concernent le passage d'un type à un autre. Ce sont les unions avec les mêmes types, mais en plus grand nombre, qui me sont le plus utiles (par exemple, lors de l'analyse d'un flux de données en série). Elles permettent l'analyse / la construction d'un encadré paquet pour devenir trivial.
typedef union
{
UINT8 buffer[PACKET_SIZE]; // Where the packet size is large enough for
// the entire set of fields (including the payload)
struct
{
UINT8 size;
UINT8 cmd;
UINT8 payload[PAYLOAD_SIZE];
UINT8 crc;
} fields;
}PACKET_T;
// This should be called every time a new byte of data is ready
// and point to the packet's buffer:
// packet_builder(packet.buffer, new_data);
void packet_builder(UINT8* buffer, UINT8 data)
{
static UINT8 received_bytes = 0;
// All range checking etc removed for brevity
buffer[received_bytes] = data;
received_bytes++;
// Using the struc only way adds lots of logic that relates "byte 0" to size
// "byte 1" to cmd, etc...
}
void packet_handler(PACKET_T* packet)
{
// Process the fields in a readable manner
if(packet->fields.size > TOO_BIG)
{
// handle error...
}
if(packet->fields.cmd == CMD_X)
{
// do stuff..
}
}
Modifier Les commentaires sur l'endianness et le remplissage des structures sont des préoccupations valables et importantes. J'ai utilisé ce corps de code presque entièrement dans des logiciels embarqués, pour lesquels j'avais le contrôle des deux extrémités du tuyau.
Il est difficile de penser à une occasion spécifique où vous auriez besoin de ce type de structure flexible, peut-être dans un protocole de message où vous enverriez des messages de tailles différentes, mais même dans ce cas, il existe probablement des alternatives meilleures et plus conviviales pour les programmeurs.
Les unions sont un peu comme les types de variantes dans d'autres langages - elles ne peuvent contenir qu'une seule chose à la fois, mais cette chose peut être un int, un float etc. selon la façon dont vous la déclarez.
Par exemple :
typedef union MyUnion MYUNION;
union MyUnion
{
int MyInt;
float MyFloat;
};
MyUnion ne pourra contenir qu'un int OU un float, en fonction de celui que vous avez défini le plus récemment . En faisant ça :
MYUNION u;
u.MyInt = 10;
u détient maintenant un int égal à 10 ;
u.MyFloat = 1.0;
u contient désormais un flottant égal à 1,0. Il ne contient plus un int. Évidemment, si vous essayez maintenant de faire printf("MyInt=%d", u.MyInt) ; alors vous obtiendrez probablement une erreur, bien que je ne sois pas sûr du comportement spécifique.
La taille de l'union est dictée par la taille de son plus grand champ, dans ce cas le flottant.