Quand utiliser les syndicats? Pourquoi avons-nous besoin d'eux?
236
Les unions sont souvent utilisées pour convertir entre les représentations binaires d'entiers et de flottants:
union
{
int i;
float f;
} u;
// Convert floating-point bits to integer:
u.f = 3.14159f;
printf("As integer: %08x\n", u.i);
Bien qu'il s'agisse d'un comportement techniquement indéfini selon la norme C (vous n'êtes censé lire que le champ le plus récemment écrit), il agira de manière bien définie dans pratiquement tous les compilateurs.
Les unions sont également parfois utilisées pour implémenter un pseudo-polymorphisme en C, en donnant à une structure une balise indiquant le type d'objet qu'elle contient, puis en réunissant les types possibles ensemble:
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;
}
}
Cela permet à la taille de struct S
n'être que de 12 octets, au lieu de 28.
Les unions sont particulièrement utiles dans la programmation intégrée ou dans les situations où un accès direct au matériel / à la mémoire est nécessaire. Voici un exemple trivial:
Ensuite, vous pouvez accéder au reg comme suit:
L'endianisme (ordre des octets) et l'architecture du processeur sont bien sûr importants.
Une autre fonctionnalité utile est le modificateur de bits:
Avec ce code, vous pouvez accéder directement à un seul bit dans l'adresse de registre / mémoire:
la source
La programmation système de bas niveau est un exemple raisonnable.
IIRC, j'ai utilisé des unions pour décomposer les registres matériels en bits de composants. Ainsi, vous pouvez accéder à un registre 8 bits (tel qu'il était, le jour où je l'ai fait ;-) dans les bits des composants.
(J'oublie la syntaxe exacte mais ...) Cette structure permettrait d'accéder à un registre de contrôle en tant que control_byte ou via les bits individuels. Il serait important de s'assurer que les bits correspondent aux bits de registre corrects pour une endianité donnée.
la source
Je l'ai vu dans quelques bibliothèques en remplacement de l'héritage orienté objet.
Par exemple
Si vous voulez que la "classe" Connection soit l'une des deux ci-dessus, vous pouvez écrire quelque chose comme:
Exemple d'utilisation dans libinfinity: http://git.0x539.de/?p=infinote.git;a=blob;f=libinfinity/common/inf-session.c;h=3e887f0d63bd754c6b5ec232948027cbbf4d61fc;hb=HEAD#l74
la source
Les unions permettent aux membres de données qui s'excluent mutuellement de partager la même mémoire. Ceci est très important lorsque la mémoire est plus rare, comme dans les systèmes embarqués.
Dans l'exemple suivant:
Cette union prendra l'espace d'un seul int, plutôt que de 3 valeurs int distinctes. Si l'utilisateur définissait la valeur de a , puis définissait la valeur de b , il écraserait la valeur de a car ils partagent tous les deux le même emplacement de mémoire.
la source
Beaucoup d'utilisations. Faites-le
grep union /usr/include/*
ou dans des répertoires similaires. La plupart des cas, leunion
est enveloppé dans unstruct
et un membre de la structure indique à quel élément de l'union accéder. Par exemple, vérifierman elf
les implémentations réelles.C'est le principe de base:
la source
Voici un exemple d'une union de ma propre base de code (de mémoire et paraphrasée donc elle peut ne pas être exacte). Il a été utilisé pour stocker des éléments de langage dans un interpréteur que j'ai construit. Par exemple, le code suivant:
se compose des éléments de langage suivants:
Les éléments de langage ont été définis comme
#define
des valeurs ' ' ainsi:et la structure suivante a été utilisée pour stocker chaque élément:
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 que ce soient des valeurs typiques, les tailles réelles dépendent de l'implémentation).
Afin de créer un élément "set", vous utiliseriez:
Afin de créer un élément "variable [b]", vous utiliseriez:
Afin de créer un élément "constant [7]", vous utiliseriez:
et vous pouvez facilement l'étendre pour inclure float (
float flt
) ou rationals (struct ratnl {int num; int denom;}
) et d'autres types.La prémisse de base est que les
str
etval
ne sont pas contigus en mémoire, ils se chevauchent, 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 sur l'emplacement de la mémoire0x1010
et les entiers et les pointeurs sont tous les deux 4 octets:Si c'était juste dans une structure, cela ressemblerait à ceci:
la source
make sure you free this later
commentaire doit-il être supprimé de l'élément constant?Je dirais que cela facilite la réutilisation de la mémoire qui pourrait être utilisée de différentes manières, c'est-à-dire économiser de la mémoire. Par exemple, vous souhaitez faire une structure "variante" capable d'enregistrer une chaîne courte ainsi qu'un nombre:
Dans un système 32 bits, cela entraînerait l'utilisation d'au moins 96 bits ou 12 octets pour chaque instance de
variant
.En utilisant une union, vous pouvez réduire la taille à 64 bits ou 8 octets:
Vous pouvez économiser encore plus si vous souhaitez ajouter plus de types de variables différents, etc. Il est possible que vous puissiez faire des choses similaires en créant un pointeur vide - mais l'union le rend beaucoup plus accessible ainsi que le type 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.
la source
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 différentes tailles de messages, mais même dans ce cas, il existe probablement des alternatives meilleures et plus conviviales pour les programmeurs.
Les unions sont un peu comme des types de variantes dans d'autres langues - elles ne peuvent contenir qu'une seule chose à la fois, mais cette chose peut être un entier, un flottant, etc., selon la façon dont vous la déclarez.
Par exemple:
MyUnion ne contiendra qu'un int OU un flottant, selon celui que vous avez défini le plus récemment . Ce faisant:
u détient maintenant un int égal à 10;
u détient désormais un flottant égal à 1,0. Il ne contient plus d'int. Évidemment maintenant si vous essayez de faire printf ("MyInt =% d", u.MyInt); vous obtiendrez probablement une erreur, même si je ne suis 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 flotteur.
la source
sizeof(int) == sizeof(float)
(== 32
) généralement.Les unions sont utilisées lorsque vous souhaitez modéliser des structures définies par du matériel, des périphériques ou des protocoles réseau, ou lorsque vous créez un grand nombre d'objets et souhaitez économiser de l'espace. Vous n'avez vraiment pas besoin d'eux 95% du temps, restez avec du code facile à déboguer.
la source
Beaucoup de ces réponses concernent la conversion d'un type à un autre. J'obtiens le plus d'utilisation des unions avec les mêmes types juste un peu plus (c'est-à-dire lors de l'analyse d'un flux de données série). Ils permettent à l'analyse / construction d'un paquet encadré de devenir trivial.
Modifier Les commentaires sur l'endianité et le remplissage de struct sont des préoccupations valables et importantes. J'ai utilisé ce corps de code presque entièrement dans des logiciels embarqués, dont la plupart j'avais le contrôle des deux extrémités du tuyau.
la source
Les syndicats sont super. Une utilisation intelligente des syndicats que j'ai vu consiste à les utiliser lors de la définition d'un événement. Par exemple, vous pouvez décider qu'un événement est de 32 bits.
Maintenant, à l'intérieur de ces 32 bits, vous aimeriez peut-être désigner les 8 premiers bits comme identifiant de l'expéditeur de l'événement ... Parfois, vous traitez l'événement dans son ensemble, parfois vous le disséquez et comparez ses composants. les syndicats vous donnent la flexibilité de faire les deux.
la source
Qu'en est-il
VARIANT
qui est utilisé dans les interfaces COM? Il a deux champs - "type" et une union contenant une valeur réelle qui est traitée en fonction du champ "type".la source
À l'école, j'ai utilisé des syndicats comme celui-ci:
Je l'ai utilisé pour gérer les couleurs plus facilement, au lieu d'utiliser les opérateurs >> et <<, je n'ai eu qu'à parcourir les différents index de mon tableau de caractères.
la source
J'ai utilisé l'union lorsque je codais pour des appareils intégrés. J'ai C int qui est de 16 bits de long. Et je dois récupérer les 8 bits supérieurs et les 8 bits inférieurs lorsque je dois lire / stocker dans l'EEPROM. J'ai donc utilisé cette méthode:
Il ne nécessite pas de décalage, le code est donc plus facile à lire.
D'un autre côté, j'ai vu un ancien code stl C ++ qui utilisait l'union pour l'allocateur stl. Si vous êtes intéressé, vous pouvez lire le code source sgi stl . En voici un extrait:
la source
struct
autour de votrehigher
/lower
? À l'heure actuelle, les deux doivent pointer uniquement vers le premier octet.Regarde ça: gestion des commandes de tampon X.25
L'une des nombreuses commandes X.25 possibles est reçue dans un tampon et gérée en place à l'aide d'un UNION de toutes les structures possibles.
la source
Dans les premières versions de C, toutes les déclarations de structure partageraient un ensemble commun de champs. Donné:
un compilateur produirait essentiellement un tableau des tailles des structures (et éventuellement des alignements), et un tableau séparé des noms, types et décalages des membres des structures. Le compilateur ne garde pas la trace des membres appartenant à quelles structures et autorise deux structures à avoir un membre du même nom uniquement si le type et le décalage correspondent (comme pour les membres
q
destruct x
etstruct y
). Si p était un pointeur vers n'importe quel type de structure, p-> q ajouterait le décalage de "q" au pointeur p et récupérerait un "int" à partir de l'adresse résultante.Compte tenu de la sémantique ci-dessus, il était possible d'écrire une fonction qui pourrait effectuer des opérations utiles sur plusieurs types de structure de manière interchangeable, à condition que tous les champs utilisés par la fonction soient alignés avec des champs utiles dans les structures en question. C'était une fonctionnalité utile, et changer C pour valider les membres utilisés pour l'accès à la structure par rapport aux types de structures en question aurait signifié la perdre en l'absence d'un moyen d'avoir une structure pouvant contenir plusieurs champs nommés à la même adresse. L'ajout de types «union» à C a aidé à combler quelque peu cette lacune (mais pas à mon humble avis, comme il aurait dû l'être).
Un élément essentiel de la capacité des syndicats à combler cette lacune était le fait qu'un pointeur vers un membre d'union pouvait être converti en pointeur vers n'importe quel syndicat contenant ce membre, et qu'un pointeur vers n'importe quel syndicat pouvait être converti en pointeur vers n'importe quel membre. Bien que la norme C89 ne dise pas expressément que la conversion
T*
directe d'un aU*
était équivalente à la conversion en un pointeur vers tout type d'union contenant les deuxT
etU
, puis à la conversionU*
, aucun comportement défini de la dernière séquence de conversion ne serait affecté par le type d'union utilisé, et la norme n'a pas spécifié de sémantique contraire pour une conversion directe deT
àU
. De plus, dans les cas où une fonction a reçu un pointeur d'origine inconnue, le comportement d'écriture d'un objet viaT*
, la conversion duT*
unU*
, puis lire l'objet viaU*
équivaudrait à écrire une union via membre de typeT
et à lire comme typeU
, qui serait défini dans certains cas (par exemple lors de l'accès aux membres de la séquence initiale commune) et défini par l'implémentation (plutôt qu'indéfini) ) pour le reste. S'il était rare que des programmes exploitent les garanties de la CEI avec des objets réels de type syndical, il était beaucoup plus courant d'exploiter le fait que les pointeurs vers des objets d'origine inconnue devaient se comporter comme des pointeurs vers des membres du syndicat et avoir les garanties comportementales qui y étaient associées.la source
foo
est unint
avec un décalage 8, celaanyPointer->foo = 1234;
signifie "prendre l'adresse dans anyPointer, la déplacer de 8 octets et effectuer un stockage entier de la valeur 1234 à l'adresse résultante. Le compilateur n'aurait pas besoin de savoir ou de se soucier s'il estanyPointer
identifié tout type de structure ayantfoo
anyPointer
identifie avec un membre struct, alors comment le compilateur vérifie-t-il ces conditionsto have a member with the same name only if the type and offset matched
de votre publication?p->foo
dépendrait du type et du décalage defoo
. Essentiellement,p->foo
c'était un raccourci pour*(typeOfFoo*)((unsigned char*)p + offsetOfFoo)
. Quant à votre dernière question, lorsqu'un compilateur rencontre une définition de membre struct, il requiert qu'aucun membre portant ce nom n'existe, ou que le membre portant ce nom ait le même type et le même décalage; Je suppose que le aurait crié si une définition de membre struct non correspondant existait, mais je ne sais pas comment il a géré les erreurs.Un exemple simple et très utile est ...
Imaginer:
vous avez un
uint32_t array[2]
et souhaitez accéder aux 3e et 4e octets de la chaîne Byte. tu pourrais faire*((uint16_t*) &array[1])
. Mais cela enfreint malheureusement les règles strictes d'alias!Mais les compilateurs connus vous permettent de faire ce qui suit:
techniquement, il s'agit toujours d'une violation des règles. mais toutes les normes connues prennent en charge cette utilisation.
la source