Quand quelqu'un utiliserait-il un syndicat? Est-ce un vestige des jours C-only?

133

J'ai appris mais je n'ai pas vraiment de syndicats. Chaque texte C ou C ++ que je traverse les présente (parfois en passant), mais ils ont tendance à donner très peu d'exemples pratiques expliquant pourquoi et où les utiliser. Quand les syndicats seraient-ils utiles dans un cas moderne (ou même hérité)? Mes deux seules hypothèses seraient de programmer des microprocesseurs lorsque vous disposez d'un espace très limité pour travailler, ou lorsque vous développez une API (ou quelque chose de similaire) et que vous voulez forcer l'utilisateur final à n'avoir qu'une seule instance de plusieurs objets / types à une fois. Ces deux suppositions sont-elles encore proches de la bonne?

Russel
la source
31
C / C ++ n'est pas un langage. Les unions sont modérément utiles en C et largement inutiles en C ++. Il serait correct de dire qu'en C ++ ils sont un "vestige de C ++ basé sur C", mais ne veut pas dire qu'ils sont "un vestige des jours C seulement" comme si C ++ supplantait C.
R .. GitHub STOP HELPING ICE
12
Pouvez-vous nous expliquer ce qu'est le substitut du c ++ aux unions, ou pourquoi ils sont inutiles en c ++?
Russel
3
Le substitut de C ++ aux unions est les classes et l'héritage - les unions en C sont presque exclusivement utilisées pour le polymorphisme de type sécurisé. Quelque chose que les cours sont bien meilleurs. (Voir la réponse de vz0 pour le polymorphisme de style C)
tobyodavies
6
@R ..: union sont encore modérément utiles en C ++. Voir les réponses ci-dessous.
Michael
2
Les unions peuvent être extrêmement précieuses dans les entrailles d'un système d'exploitation ou, par exemple, dans un package qui assemble / désassemble des fichiers audio. Dans de tels contextes, ils sont utilisés de différentes manières - conversion de données / endian, polymorphisme de bas niveau, et al. Oui, il existe d'autres solutions au même problème (principalement la conversion entre les types de pointeurs), mais les unions sont souvent plus propres et mieux auto-documentées.
Hot Licks

Réponses:

105

Les syndicats sont généralement utilisés avec la société d'un discriminateur: une variable indiquant lequel des champs de l'union est valide. Par exemple, disons que vous souhaitez créer votre propre type de Variant :

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

Ensuite, vous l'utiliseriez comme:

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

C'est en fait un idiome assez courant, en particulier sur les composants internes de Visual Basic.

Pour un exemple concret, consultez l' union SDL_Event de SDL . ( code source réel ici ). Il y a un typechamp en haut de l'union, et le même champ est répété sur chaque structure d'événement SDL_ *. Ensuite, pour gérer l'événement correct, vous devez vérifier la valeur du typechamp.

Les avantages sont simples: il existe un seul type de données pour gérer tous les types d'événements sans utiliser de mémoire inutile.

vz0
la source
2
Génial! Dans ce cas, je me demande maintenant pourquoi la fonction Sdl n'a pas été simplement implémentée en tant que hiérarchie de classes. Est-ce pour le rendre compatible C et pas seulement C ++?
Russel
12
Les classes @Russel C ++ ne peuvent pas être utilisées à partir d'un programme C, mais les structures / unions C peuvent être facilement accédées à partir de C ++ en utilisant un bloc 'extern "C"'.
vz0
1
Ce modèle de variante est également souvent utilisé pour les interpréteurs de langage de programmation, par exemple la définition de struct objectdans github.com/petermichaux/bootstrap-scheme/blob/v0.21/scheme.c
Adam Rosenfield
1
Explication géniale. J'ai toujours su ce qu'étaient les syndicats, mais je n'ai jamais vu de raison réelle pour laquelle quelqu'un serait assez fou pour les utiliser :) Merci pour l'exemple.
riwalk
@ Stargazer712, la recherche de codes de Google: google.com/…
kagali-san
87

Je trouve les syndicats C ++ plutôt cool. Il semble que les gens ne pensent généralement qu'au cas d'utilisation où l'on veut changer la valeur d'une instance d'union "en place" (qui, semble-t-il, ne sert qu'à économiser de la mémoire ou à effectuer des conversions douteuses).

En fait, les syndicats peuvent être d'une grande puissance en tant qu'outil d'ingénierie logicielle, même si vous ne modifiez jamais la valeur d'une instance d'union .

Cas d'utilisation 1: le caméléon

Avec les unions, vous pouvez regrouper un certain nombre de classes arbitraires sous une dénomination, ce qui n'est pas sans similitudes avec le cas d'une classe de base et de ses classes dérivées. Ce qui change, cependant, c'est ce que vous pouvez et ne pouvez pas faire avec une instance d'union donnée:

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

Il apparaît que le programmeur doit être certain du type de contenu d'une instance d'union donnée lorsqu'il veut l'utiliser. C'est le cas en fonction fci-dessus. Cependant, si une fonction recevait une instance d'union en tant qu'argument passé, comme c'est le cas avec gci-dessus, alors elle ne saurait pas quoi en faire. La même chose s'applique aux fonctions retournant une instance d'union, voir h: comment l'appelant sait-il ce qu'il y a à l'intérieur?

Si une instance d'union n'est jamais passée comme argument ou comme valeur de retour, alors elle aura forcément une vie très monotone, avec des pics d'excitation lorsque le programmeur choisit de changer son contenu:

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

Et c'est le cas d'utilisation le plus (non) populaire des syndicats. Un autre cas d'utilisation est lorsqu'une instance d'union vient avec quelque chose qui vous indique son type.

Cas d'utilisation 2: "Ravi de vous rencontrer, je suis objectde Class"

Supposons qu'un programmeur choisisse de toujours jumeler une instance d'union avec un descripteur de type (je laisserai à la discrétion du lecteur le soin d'imaginer une implémentation pour un tel objet). Cela va à l'encontre du but de l'union elle-même si ce que veut le programmeur est d'économiser de la mémoire et que la taille du descripteur de type n'est pas négligeable par rapport à celle de l'union. Mais supposons qu'il soit crucial que l'instance d'union puisse être passée comme argument ou comme valeur de retour avec l'appelé ou l'appelant ne sachant pas ce qu'il y a à l'intérieur.

Ensuite, le programmeur doit écrire une switchinstruction de flux de contrôle pour distinguer Bruce Wayne d'un bâton en bois, ou quelque chose d'équivalent. Ce n'est pas trop mal quand il n'y a que deux types de contenus dans le syndicat mais évidemment, le syndicat ne s'adapte plus.

Cas d'utilisation 3:

Comme les auteurs d' une recommandation pour la norme ISO C ++ l' ont remis en 2008,

De nombreux domaines problématiques importants nécessitent un grand nombre d'objets ou des ressources mémoire limitées. Dans ces situations, il est très important de conserver de l'espace et une union est souvent un moyen idéal pour y parvenir. En fait, un cas d'utilisation courant est la situation où un syndicat ne change jamais de membre actif au cours de sa vie. Il peut être construit, copié et détruit comme s'il s'agissait d'une structure contenant un seul membre. Une application typique de ceci serait de créer une collection hétérogène de types non liés qui ne sont pas alloués dynamiquement (peut-être sont-ils construits en place dans une carte, ou membres d'un tableau).

Et maintenant, un exemple, avec un diagramme de classes UML:

de nombreuses compositions pour la classe A

La situation en anglais simple: un objet de classe A peut avoir des objets de n'importe quelle classe parmi B1, ..., Bn, et au plus un de chaque type, n étant un assez grand nombre, disons au moins 10.

Nous ne voulons pas ajouter des champs (membres de données) à A comme ceci:

private:
    B1 b1;
    .
    .
    .
    Bn bn;

parce que n peut varier (nous pourrions vouloir ajouter des classes Bx au mélange), et parce que cela causerait un désordre avec les constructeurs et parce que les objets A prendraient beaucoup de place.

Nous pourrions utiliser un conteneur farfelu de void*pointeurs vers des Bxobjets avec des castes pour les récupérer, mais c'est fugace et donc de style C ... mais plus important encore, cela nous laisserait la durée de vie de nombreux objets alloués dynamiquement à gérer.

Au lieu de cela, ce qui peut être fait est ceci:

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

Ensuite, pour obtenir le contenu d'une instance d'union data, vous utilisez a.get(TYPE_B2).b2et les likes, où aest une Ainstance de classe .

C'est d'autant plus puissant que les unions sont illimitées dans C ++ 11. Voir le document lié ci - dessus ou cet article pour plus de détails.

jrsala
la source
Cela a été très utile et la série de ce deuxième article était très informative. Merci.
Andrew le
38

Un exemple est dans le domaine intégré, où chaque bit d'un registre peut signifier quelque chose de différent. Par exemple, une union d'un entier de 8 bits et d'une structure avec 8 champs de bits séparés de 1 bit vous permet de changer un bit ou l'octet entier.

Kevin
la source
7
Ceci est également très courant dans les pilotes de périphériques. Il y a quelques années, j'ai écrit beaucoup de code en utilisant des syndicats comme celui-ci pour un projet. Ce n'est normalement pas recommandé, et cela peut être spécifique au compilateur dans certains cas, mais cela fonctionne.
thkala
11
Je n'appellerais pas cela "non recommandé". Dans l'espace intégré, il est souvent beaucoup plus propre et moins sujet aux erreurs que les alternatives, qui impliquent généralement de nombreux casts et void*s explicites ou des masques et des décalages.
bta
il h? Beaucoup de castings explicites? Cela me semble des déclarations simples comme REG |= MASKet REG &= ~MASK. Si cela est sujet aux erreurs, mettez-les dans un #define SETBITS(reg, mask)et #define CLRBITS(reg, mask). Ne comptez pas sur le compilateur pour obtenir les bits dans un certain ordre ( stackoverflow.com/questions/1490092/… )
Michael
26

Herb Sutter a écrit dans GOTW il y a environ six ans, en insistant sur :

"Mais ne pensez pas que les syndicats ne sont que des vestiges des temps anciens. Les syndicats sont peut-être plus utiles pour économiser de l'espace en permettant aux données de se chevaucher, et cela est toujours souhaitable en C ++ et dans le monde moderne d'aujourd'hui. Par exemple, certains des plus C ++ avancéles implémentations de bibliothèques standard dans le monde utilisent maintenant cette technique pour implémenter la "petite optimisation de chaîne", une excellente alternative d'optimisation qui réutilise le stockage à l'intérieur d'un objet chaîne lui-même: pour les grandes chaînes, l'espace à l'intérieur de l'objet chaîne stocke le pointeur habituel vers le la mémoire tampon allouée et les informations de gestion telles que la taille de la mémoire tampon; pour les petites chaînes, le même espace est à la place réutilisé pour stocker directement le contenu de la chaîne et éviter complètement toute allocation dynamique de mémoire. Pour plus d'informations sur l'optimisation des petites chaînes (et d'autres optimisations et pessimisations de chaînes de manière très approfondie), voir ... "."

Et pour un exemple moins utile, voyez la question longue mais peu concluante gcc, strict-aliasing et casting through a union .

Joseph Quinsey
la source
23

Eh bien, un exemple de cas d'utilisation auquel je peux penser est celui-ci:

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

Vous pouvez ensuite accéder aux parties séparées 8 bits de ce bloc de données 32 bits; cependant, préparez-vous à être potentiellement mordu par l'endian.

Ceci n'est qu'un exemple hypothétique, mais chaque fois que vous souhaitez fractionner des données dans un champ en composants comme celui-ci, vous pouvez utiliser une union.

Cela dit, il existe également une méthode qui est sûre pour les endians:

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

Par exemple, puisque cette opération binaire sera convertie par le compilateur à l'endianness correcte.


la source
Je pense que la meilleure question est de savoir quand utiliser les syndicats. Vous avez fourni une réponse sur les cas où un syndicat n'est pas le bon outil, ce qui, à mon avis, devrait être plus clair dans cette réponse.
Michael
15

Quelques utilisations pour les syndicats:

  • Fournit une interface d'endianness générale à un hôte externe inconnu.
  • Manipulez les données à virgule flottante de l'architecture CPU étrangère, comme l'acceptation de VAX G_FLOATS à partir d'une liaison réseau et leur conversion en longs réels IEEE 754 pour traitement.
  • Fournissez un accès simple à un type de niveau supérieur.
union {
      unsigned char   byte_v[16];
      long double     ld_v;
 }

Avec cette déclaration, il est simple d'afficher les valeurs d'octet hexadécimal de a long double, de changer le signe de l'exposant, de déterminer s'il s'agit d'une valeur dénormale ou d'implémenter une longue double arithmétique pour un CPU qui ne le prend pas en charge, etc.

  • Économie d'espace de stockage lorsque les champs dépendent de certaines valeurs:

    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 les fichiers include à utiliser avec votre compilateur. Vous trouverez des dizaines à des centaines d'utilisations 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 {
marcher
la source
5
TTT ... TTT! Deux votes négatifs et aucune explication. C'est décevant.
wallyk
L'exemple avec une personne qui peut tenir un homme et une femme est très mauvais à mes yeux. Pourquoi pas une classe de base de personne et une classe de base homme et femme? Désolé, mais rechercher manuellement une variable pour déterminer le type stocké dans un champ de données est une mauvaise idée du tout. Il s'agit d'un code C artisanal jamais vu depuis des années. Mais pas de vote négatif, ce n'est que mon point de vue :-)
Klaus
4
Je suppose que vous avez obtenu les votes négatifs pour l'union «castrée» ou «grossesse». C'est un peu malade.
akaltar le
2
Ouais, je suppose que c'était une journée sombre.
wallyk
14

Les unions sont utiles pour traiter des données de niveau octet (bas niveau).

Une de mes utilisations récentes concernait la modélisation d'adresse IP qui ressemble à ci-dessous:

// 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
la source
7
Gardez cependant à l'esprit que l'accès à des éléments bruts comme celui-ci n'est pas standard et peut ne pas fonctionner comme prévu avec tous les compilateurs.
nos
3
En outre, il est très courant de voir cela utilisé d'une manière qui ne garantit pas l'alignement, ce qui est un comportement non défini.
Mooing Duck le
10

Un exemple quand j'ai utilisé un syndicat:

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

        };
...
}

cela me permet d'accéder à mes données sous forme de tableau ou d'éléments.

J'ai utilisé un syndicat pour que les différents termes indiquent la même valeur. En traitement d'image, que je travaille sur les colonnes ou la largeur ou la taille dans le sens X, cela peut devenir déroutant. Pour résoudre ce problème, j'utilise un syndicat afin de savoir quelles descriptions vont ensemble.

   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
la source
12
Notez que bien que cette solution fonctionne sur la plupart des plates-formes observables, définir des valeurs sur _x, _y, _z et accéder à _coord est un comportement non défini. Le principal objectif des syndicats est la préservation de l'espace. Vous devez accéder exactement au même élément union que vous avez précédemment défini.
anxieux
1
c'est comme ça que je l'utilise aussi, bien que j'utilise un std :: array pour les coords, et quelques static_asserts
Viktor Sehr
1
Ce code enfreint les règles strictes d'aliasing et ne doit pas être recommandé.
Walter
Existe-t-il peut-être un moyen d'améliorer le syndicat de manière à ce qu'il soit fiable de le faire?
Andrew
8

Les syndicats fournissent le polymorphisme chez C.

Match nul
la source
18
Je pensais avoir void*fait ça ^^
2
@ user166390 Le polymorphisme utilise la même interface pour manipuler plusieurs types; void * n'a pas d'interface.
Alice
2
En C, le polymorphisme est généralement implémenté via des types opaques et / ou des pointeurs de fonction. Je ne sais pas comment ni pourquoi vous utiliseriez un syndicat pour y parvenir. Cela ressemble vraiment à une mauvaise idée.
Lundin
7

Une utilisation brillante de l'union est l'alignement de la mémoire, que j'ai trouvé dans le code source PCL (Point Cloud Library). La structure de données unique dans l'API peut cibler deux architectures: CPU avec prise en charge SSE ainsi que CPU sans prise en charge SSE. Par exemple: la structure de données de PointXYZ est

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

Les 3 flotteurs sont rembourrés avec un flotteur supplémentaire pour l'alignement SSE. Donc pour

PointXYZ point;

L'utilisateur peut accéder à point.data [0] ou point.x (selon le support SSE) pour accéder, par exemple, à la coordonnée x. Des détails d'utilisation plus similaires sont disponibles sur le lien suivant: Documentation PCL Types PointT

Shubham Verma
la source
7

Le unionmot-clé, bien qu'il soit encore utilisé en C ++ 03 1 , est principalement un vestige des jours C. Le problème le plus flagrant est qu'il ne fonctionne qu'avec le POD 1 .

L'idée de l'union, cependant, est toujours présente, et en effet les bibliothèques Boost disposent d'une classe de type union:

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

Ce qui présente la plupart des avantages du union(sinon tous) et ajoute:

  • possibilité d'utiliser correctement les types non-POD
  • sécurité de type statique

Dans la pratique, il a été démontré qu'il était équivalent à une combinaison de union+ enum, et évalué qu'il était aussi rapide (alors que boost::anyc'est plus du domaine de dynamic_cast, puisqu'il utilise RTTI).

1 Les unions ont été mises à niveau en C ++ 11 ( unions illimitées ) et peuvent désormais contenir des objets avec des destructeurs, bien que l'utilisateur doive invoquer le destructeur manuellement (sur le membre de l'union actuellement actif). Il est toujours beaucoup plus facile d'utiliser des variantes.

Matthieu M.
la source
Ce n'est plus le cas dans les versions plus récentes de c ++. Voir la réponse de jrsala, par exemple.
Andrew
@Andrew: J'ai mis à jour la réponse pour mentionner que C ++ 11, avec des unions illimitées, autorisait le stockage des types avec des destructeurs en union. Je soutiens toujours ma position selon laquelle il vaut vraiment mieux utiliser des syndicats étiquetés, par exemple, boost::variantque d'essayer d'utiliser les syndicats seuls. Il y a beaucoup trop de comportements indéfinis dans les syndicats pour que vos chances de réussir sont abyssales.
Matthieu M.
3

Extrait de l'article de Wikipédia sur les syndicats :

La principale utilité d'une union est de conserver de l'espace , car elle permet de stocker de nombreux types différents dans le même espace. Les syndicats fournissent également un polymorphisme brut . Cependant, il n'y a pas de vérification des types, c'est donc au programmeur de s'assurer que les champs appropriés sont accessibles dans différents contextes. Le champ pertinent d'une variable union est généralement déterminé par l'état d'autres variables, éventuellement dans une structure englobante.

Un idiome de programmation C courant utilise des unions pour effectuer ce que C ++ appelle un reinterpret_cast, en affectant un champ d'une union et en lisant un autre, comme cela se fait dans le code qui dépend de la représentation brute des valeurs.

Thkala
la source
2

Dans les premiers jours de C (par exemple comme documenté en 1974), toutes les structures partageaient un espace de noms commun pour leurs membres. Chaque nom de membre était associé à un type et à un décalage; si "wd_woozle" était un "int" à l'offset 12, alors un pointeur pde n'importe quel type de structure p->wd_woozleserait équivalent à *(int*)(((char*)p)+12). Le langage exigeait que tous les membres de tous les types de structures aient des noms uniques, sauf qu'il autorisait explicitement la réutilisation des noms de membres dans les cas où chaque structure où ils étaient utilisés les traitait comme une séquence initiale commune.

Le fait que les types de structure puissent être utilisés de manière promiscue a permis aux structures de se comporter comme si elles contenaient des champs se chevauchant. Par exemple, des définitions données:

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

code pourrait déclarer une structure de type "float1" et ensuite utiliser "membres" b0 ... b3 pour y accéder aux octets individuels. Lorsque la langue était modifiée pour que chaque structure reçoive un espace de noms distinct pour ses membres, le code qui reposait sur la possibilité d'accéder aux choses de plusieurs manières se cassait. Les valeurs de séparation des espaces de noms pour différents types de structure étaient suffisantes pour exiger que ce code soit modifié pour l'adapter, mais la valeur de ces techniques était suffisante pour justifier l'extension du langage pour continuer à le supporter.

Code qui avait été écrit pour exploiter la capacité d'accéder au stockage dans un struct float1comme si elle était un struct byte4pourrait être fait pour travailler dans la nouvelle langue en ajoutant une déclaration: union f1b4 { struct float1 ff; struct byte4 bb; };, déclarant des objets en tant que type union f1b4;plutôt que struct float1, et le remplacement des accès à f0, b0, b1, etc. . avec ff.f0, bb.b0, bb.b1, etc. Bien qu'il existe de meilleures façons ce code aurait pu être pris en charge, l' unionapproche était au moins un peu réalisable, au moins des interprétations C89 datant de l' époque des règles d'aliasing.

supercat
la source
1

Disons que vous avez n types de configurations différents (étant simplement un ensemble de variables définissant des paramètres). En utilisant une énumération des types de configuration, vous pouvez définir une structure qui a l'ID du type de configuration, ainsi qu'une union de tous les différents types de configurations.

De cette façon, partout où vous passez la configuration peut utiliser l'ID pour déterminer comment interpréter les données de configuration, mais si les configurations étaient énormes, vous ne seriez pas obligé d'avoir des structures parallèles pour chaque type potentiel gaspillant de l'espace.

Gavin H
la source
1

Un coup de pouce récent sur l'importance déjà élevée des unions a été donné par la règle stricte d'aliasing introduite dans la version récente du standard C.

Vous pouvez utiliser les unions do pour taper des punaises sans violer la norme C.
Ce programme a un comportement non spécifié (car je l'ai supposé floatet unsigned inta la même longueur) mais pas un comportement indéfini (voir ici ).

#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;
}
Communauté
la source
Les règles d'accès au type ne se trouvent pas uniquement dans les versions "récentes" de la norme. Chaque version du C a inclus essentiellement les mêmes règles. Ce qui a changé, c'est que les compilateurs avaient l'habitude de considérer la note de bas de page "L'intention de cette liste est de spécifier les circonstances dans lesquelles un objet peut ou non être aliasé." comme indiquant que la règle n'était pas censée s'appliquer dans les cas qui n'impliquaient pas d'alias tel qu'écrit , mais ils la traitent maintenant comme une invitation à réécrire du code pour créer des alias là où il n'y en avait pas eu.
supercat
1

Je voudrais ajouter un bon exemple pratique d'utilisation de l'union - implémentation d'un calculateur / interprète de formule ou utilisation d'une sorte de calcul dans le calcul (par exemple, vous voulez utiliser des parties modifiables pendant l'exécution de vos formules de calcul - résoudre une équation numériquement - juste par exemple). Vous voudrez peut-être définir des nombres / constantes de différents types (nombres entiers, flottants, même complexes) comme ceci:

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

Donc, vous économisez de la mémoire et ce qui est plus important - vous évitez toute allocation dynamique pour une quantité probablement extrême (si vous utilisez beaucoup de nombres définis au moment de l'exécution) de petits objets (par rapport aux implémentations via l'héritage de classe / polymorphisme). Mais ce qui est plus intéressant, vous pouvez toujours utiliser la puissance du polymorphisme C ++ (si vous êtes fan du double dispatching, par exemple;) avec ce type de struct. Ajoutez simplement un pointeur d'interface "factice" à la classe parente de tous les types de nombres en tant que champ de cette structure, pointant vers cette instance au lieu de / en plus du type brut, ou utilisez de bons vieux pointeurs de fonction 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);
 }
}

vous pouvez donc utiliser le polymorphisme au lieu des vérifications de type avec switch (type) - avec une implémentation efficace en mémoire (pas d'allocation dynamique de petits objets) - si vous en avez besoin, bien sûr.

Cerveau
la source
Cela peut être utile lors de la création d'un langage dynamique. Le problème que je pense que cela résoudra est de modifier en masse une variable de type inconnu sans implémenter cette modification N fois. Les macros sont horribles pour cela et la création de modèles est pratiquement impossible.
Andrew
0

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

Les utilisations de l'union sont rares. Sur la plupart des ordinateurs, la taille d'un pointeur et d'un int sont généralement les mêmes - c'est parce que les deux s'inscrivent généralement dans un registre du processeur. Donc, si vous voulez faire un cast rapide et sale d'un pointeur vers un int ou l'inverse, déclarez une union.

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

Une autre utilisation d'une union est dans une commande ou un protocole de message où des messages de taille différente sont envoyés et reçus. Chaque type de message contiendra des informations différentes mais chacun aura une partie fixe (probablement une structure) et un bit de partie variable. Voici comment vous pourriez l'implémenter.

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

struct msgstring80 {structure tête fixe; message char [80]; }
struct msgint10 {structure tête fixe; message int [10]; } struct msgack {structure tête fixe; int ok; } union messagetype {
struct msgstring50 m50; struct msgstring80 m80; struct msgint10 i10; struct msgack ack; }

En pratique, bien que les unions soient de la même taille, il est logique d'envoyer uniquement les données significatives et non l'espace perdu. Un msgack ne fait que 16 octets tandis qu'un msgstring80 fait 92 octets. Ainsi, lorsqu'une variable de type de message est initialisée, son champ de taille est défini en fonction de son type. Cela peut ensuite être utilisé par d'autres fonctions pour transférer le nombre correct d'octets.

ανώνυμος
la source
0

Les syndicats offrent un moyen de manipuler différents types de données dans une seule zone de stockage sans intégrer aucune information indépendante de la machine dans le programme.Ils sont analogues aux enregistrements de variantes dans pascal

À titre d'exemple comme celui que l'on peut trouver dans un gestionnaire de table de symboles de compilateur, supposons qu'une constante puisse être un int, un flottant ou un pointeur de caractère. La valeur d'une constante particulière doit être stockée dans une variable du type approprié, mais il est plus pratique pour la gestion de table si la valeur occupe la même quantité de stockage et est stockée au même endroit quel que soit son type. C'est le but d'une union - une variable unique qui peut légitimement contenir l'un des différents types. La syntaxe est basée sur des structures:

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

La variable u sera suffisamment grande pour contenir le plus grand des trois types; la taille spécifique dépend de l'implémentation. N'importe lequel de ces types peut être attribué à u puis utilisé dans des expressions, tant que l'utilisation est cohérente

Khushal
la source