web-dev-qa-db-fra.com

Pourquoi avons-nous besoin des syndicats C?

Quand faut-il utiliser les syndicats? Pourquoi avons-nous besoin d'eux?

219
ramu

Les unions sont souvent utilisées pour convertir entre les représentations binaires d'entiers et les 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 non défini techniquement au standard 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 aussi parfois utilisées pour implémenter le pseudo-polymorphisme en C, en attribuant à une structure une balise indiquant le type d'objet qu'il contient, puis en unissant les types possibles:

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 ne doit comporter que 12 octets au lieu de 28.

240
Adam Rosenfield

Les syndicats 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:

typedef union
{
    struct {
        unsigned char byte1;
        unsigned char byte2;
        unsigned char byte3;
        unsigned char byte4;
    } bytes;
    unsigned int dword;
} HW_Register;
HW_Register reg;

Ensuite, vous pouvez accéder au registre comme suit:

reg.dword = 0x12345678;
reg.bytes.byte3 = 4;

La finalité (ordre des octets) et l'architecture du processeur sont bien sûr importants.

Une autre fonctionnalité utile est le modificateur de 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;

Avec ce code, vous pouvez accéder directement à un seul bit dans l’adresse de registre/mémoire:

x = reg.bits.b2;
124
kgiannakakis

La programmation système de bas niveau est un exemple raisonnable.

IIRC, j'ai utilisé des syndicats pour décomposer les registres de matériel en bits de composant. Ainsi, vous pouvez accéder à un registre 8 bits (tel qu'il était, le jour où j'ai fait cela ;-) dans les bits de composant.

(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.

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;
60
Snips

Je l'ai déjà vu dans quelques bibliothèques pour remplacer l'héritage orienté objet.

Par exemple.

        Connection
     /       |       \
  Network   USB     VirtualConnection

Si vous voulez que la "classe" Connection soit l'une des deux ci-dessus, vous pouvez écrire quelque chose comme:

struct Connection
{
    int type;
    union
    {
        struct Network network;
        struct USB usb;
        struct Virtual virtual;
    }
};

Exemple d'utilisation dans libinfinity: http://git.0x539.de/?p=infinote.git;a=blob;f=libinfinity/common/inf-session.c;h=3e887f0d63bd754c6b5ec232948027cbbf4d61fc;hb l74

34
bb-generation

Les unions permettent aux membres de données mutuellement exclusifs 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:

union {
   int a;
   int b;
   int c;
} myUnion;

Cette union occupera l'espace d'un seul int, plutôt que 3 valeurs int distinctes. Si l'utilisateur définit la valeur de a, puis la valeur de b, il écrasera la valeur de a car ils partagent tous les deux le même emplacement mémoire.

29

Beaucoup d'usages. Il suffit de faire grep union /usr/include/* ou dans des répertoires similaires. La plupart des cas, le union est encapsulé dans un struct et un membre de la structure indique à quel élément de l'union accéder. Par exemple, la caisse man elf pour des implémentations réelles.

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

Voici un exemple d'union de ma propre base de code (de mémoire et paraphrasée afin que ce ne soit peut-être pas exact). Il a été utilisé pour stocker des éléments de langage dans un interprète que j'ai construit. Par exemple, le code suivant:

set a to b times 7.

se compose des éléments de langage suivants:

  • symbole [set]
  • variable [a]
  • symbole [à]
  • variable [b]
  • symbole [fois]
  • constante [7]
  • symbole[.]

Les éléments de langage ont été définis comme '#define 'valorise ainsi:

#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 que ce soient des valeurs typiques, les tailles réelles correspondent à l'implémentation).

Pour créer un élément "set", vous utiliseriez:

tElem e;
e.typ = ELEM_SYM_SET;

Pour créer un élément "variable [b]", vous utiliseriez:

tElem e;
e.typ = ELEM_VARIABLE;
e.str = strdup ("b");   // make sure you free this later

Pour créer un élément "constant [7]", vous utiliseriez:

tElem e;
e.typ = ELEM_CONSTANT;
e.val = 7;

et vous pouvez facilement l’étendre aux flottants (float flt) ou rationnels (struct ratnl {int num; int denom;}) et d'autres types.

Le principe de base est que 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ée ici, où la structure est basée à l'emplacement de la mémoire 0x1010 et les nombres entiers et les pointeurs sont tous deux 4 octets:

       +-----------+
0x1010 |           |
0x1011 |    typ    |
0x1012 |           |
0x1013 |           |
       +-----+-----+
0x1014 |     |     |
0x1015 | str | val |
0x1016 |     |     |
0x1017 |     |     |
       +-----+-----+

Si c'était juste dans une structure, cela ressemblerait à ceci:

       +-------+
0x1010 |       |
0x1011 |  typ  |
0x1012 |       |
0x1013 |       |
       +-------+
0x1014 |       |
0x1015 |  str  |
0x1016 |       |
0x1017 |       |
       +-------+
0x1018 |       |
0x1019 |  val  |
0x101A |       |
0x101B |       |
       +-------+
17
paxdiablo

Je dirais que cela facilite la réutilisation de mémoire qui peut être utilisée de différentes manières, c’est-à-dire économiser de la mémoire. Par exemple. vous voudriez faire une structure "variante" capable de sauvegarder une chaîne courte ainsi qu'un nombre:

struct variant {
    int type;
    double number;
    char *string;
};

Dans un système 32 bits, 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 plusieurs types de variables, etc. Il est peut-être vrai que vous pouvez faire des choses similaires en lançant un pointeur vide - mais l'union le rend beaucoup plus accessible, ainsi que le type sûr. De telles économies ne semblent pas énormes, mais vous économisez un tiers de la mémoire utilisée pour toutes les instances de cette structure.

7
Mario

Il est difficile de penser à une occasion spécifique où vous auriez besoin de ce type de structure flexible, par exemple 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 syndicats sont un peu comme les variantes de types dans d'autres langues: ils ne peuvent contenir qu'un objet à la fois, mais ce peut être un int, un float, etc., selon la façon dont vous le déclarez.

Par exemple:

typedef union MyUnion MYUNION;
union MyUnion
{
   int MyInt;
   float MyFloat;
};

MyUnion contiendra uniquement un int OR un float, selon ce que vous avez défini le plus récemment). Voici comment procéder:

MYUNION u;
u.MyInt = 10;

u a maintenant un int égal à 10;

u.MyFloat = 1.0;

u détient maintenant un float égal à 1,0. Il ne tient plus un int. Evidemment maintenant si vous essayez de faire printf ("MyInt =% d", u.MyInt); alors vous allez probablement avoir 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, en l'occurrence le flotteur.

5
Xiaofu

Les unions sont utilisées lorsque vous souhaitez modéliser des structures définies par un matériel, des périphériques ou des protocoles réseau, ou lorsque vous créez un grand nombre d'objets et que vous souhaitez économiser de l'espace. Vous n'en avez vraiment pas besoin 95% du temps, tenez-vous-en à du code facile à déboguer.

4
Paul Betts

Beaucoup de ces réponses traitent de la conversion d'un type à un autre. Les unions avec les mêmes types en utilisent encore plus (par exemple, lors de l'analyse d'un flux de données série). Ils permettent à l'analyse/construction d'un paquet encadré de devenir triviale.

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

Edit Les commentaires sur endianness et struct padding sont valables et très inquiétants. J'ai utilisé ce corps de code presque entièrement dans des logiciels intégrés, la plupart desquels j'avais le contrôle des deux extrémités du tuyau.

3
Adam Lewis

Qu'en est-il de VARIANT utilisé dans les interfaces COM? Il comporte deux champs - "type" et une union contenant une valeur réelle traitée en fonction du champ "type".

1
sharptooth

J'ai utilisé union lorsque je codais pour des périphériques intégrés. J'ai C int qui est 16 bits de long. Et j'ai besoin de récupérer les 8 bits les plus élevés et les 8 bits les plus bas lorsque j'ai besoin de lire/enregistrer sur EEPROM. Alors j'ai utilisé de cette façon:

union data {
    int data;
    struct {
        unsigned char higher;
        unsigned char lower;
    } parts;
};

Il n'est pas nécessaire de changer de code pour que le code soit plus facile à lire.

D'autre part, j'ai vu un vieux code stl C++ qui utilisait l'union pour stl allocator. Si vous êtes intéressé, vous pouvez lire le code source sgi stl . En voici un extrait:

union _Obj {
    union _Obj* _M_free_list_link;
    char _M_client_data[1];    /* The client sees this.        */
};
1
Mu Qiao
  • Un fichier contenant différents types d'enregistrement.
  • Une interface réseau contenant différents types de demandes.

Jetez un oeil à ceci: traitement de la commande de tampon X.25

L'une des nombreuses commandes X.25 possibles est reçue dans une mémoire tampon et gérée sur place à l'aide d'une union de toutes les structures possibles.

1
James Anderson

À l'école, j'ai utilisé des syndicats comme celui-ci:

typedef union
{
  unsigned char color[4];
  int       new_color;
}       u_color;

Je l'utilisais pour gérer les couleurs plus facilement, au lieu d'utiliser les opérateurs >> et <<, il me suffisait de parcourir les différents index de mon tableau de caractères.

1
Zoneur

Dans les premières versions de C, toutes les déclarations de structure partageaient un ensemble commun de champs. Donné:

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 compilateur produirait essentiellement un tableau des tailles (et éventuellement des alignements) des structures, ainsi qu'un tableau séparé des noms, des types et des décalages des membres des structures. Le compilateur ne gardait pas trace des membres appartenant à quelles structures et autoriserait un membre portant le même nom uniquement si le type et le décalage correspondaient (comme avec le membre q de struct x et struct y). Si p était un pointeur sur un type de structure, p-> q ajouterait le décalage de "q" au pointeur p et extrairait un "int" à partir de l'adresse résultante.

Compte tenu de la sémantique ci-dessus, il était possible d'écrire une fonction capable d'effectuer de manière interchangeable certaines opérations utiles sur plusieurs types de structures, à condition que tous les champs utilisés par la fonction soient alignés avec des champs utiles au sein des structures en question. C’était une fonctionnalité utile, et modifier C pour valider les membres utilisés pour l’accès à la structure par rapport aux types des structures en question aurait signifié la perdre en l’absence de moyen d’avoir une structure pouvant contenir plusieurs champs nommés à la même adresse. L'ajout de types "union" à C a contribué à 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 sur un membre du syndicat pouvait être transformé en un pointeur sur un syndicat contenant ce membre, et qu’un pointeur sur un syndicat pouvait être converti en un pointeur sur un membre. Bien que la norme C89 n’ait pas dit expressément que jeté un T* directement à un U* équivaut à le transformer en un pointeur sur tout type d'union contenant à la fois T et U, puis à le transférer en U*, aucun comportement défini de la dernière séquence de conversion ne serait affecté par le type d'union utilisé et la norme ne spécifiait aucune sémantique contraire pour une conversion directe de T à U. De plus, dans les cas où une fonction reçoit un pointeur d’origine inconnue, le comportement de l’écriture d’un objet via T*, en convertissant le T* à un U*, puis lecture de l'objet via U* équivaudrait à écrire une union via un membre de type T et à lire le type U, qui serait défini par défaut dans certains cas (par exemple lors de l'accès à des membres de séquence initiale commune) et Implémentation définie (plutôt qu'indéfinie) pour le reste. S'il était rare que les programmes exploitent les garanties du SIC avec des objets réels de type union, il était beaucoup plus courant d'exploiter le fait que les pointeurs sur des objets d'origine inconnue devaient se comporter comme des pointeurs vers les membres d'un syndicat et assortis des garanties de comportement associées.

1
supercat

Les syndicats sont géniaux. Une utilisation judicieuse des syndicats que j'ai vus consiste à les utiliser pour définir un événement. Par exemple, vous pouvez décider qu'un événement a 32 bits.

Maintenant, dans ces 32 bits, vous voudrez peut-être désigner les 8 premiers bits comme identifiant de l'expéditeur de l'événement ... Parfois, vous traitez avec l'événement dans son ensemble, parfois vous le disséquez et comparez ses composants. les syndicats vous donnent la possibilité de faire les deux.

 union Événement 
 {
 Code événement long, non signé; 
 Caractère non signé événementParts [4]; 
}; 
1
dicroce

Un exemple simple et très utile, est ....

Imaginer:

vous avez un uint32_t array[2] et souhaitez accéder aux 3ème et 4ème octets de la chaîne d'octets. vous pouvez faire *((uint16_t*) &array[1]). Mais cela brise tristement les règles strictes en matière de crénelage!

Mais les compilateurs connus vous permettent d'effectuer les tâches suivantes:

union un
{
    uint16_t array16[4];
    uint32_t array32[2];
}

techniquement, cela constitue toujours une violation des règles. mais toutes les normes connues supportent cette utilisation.

0
dhein