Licence CC BY-SA

Les unions

Dans la deuxième partie de ce cours nous vous avons présenté la notion d’agrégat qui recouvrait les tableaux et les structures. Toutefois, nous avons passé sous silence un dernier agrégat plus discret moins utilisé : les unions.

Définition

Une union est, à l’image d’une structure, un regroupement d’objet de type différents. La nuance, et elle est de taille, est qu’une union est un agrégat qui ne peut contenir qu'un seul de ses membres à la fois. Autrement dit, une union peut accueillir la valeur de n’importe lequel de ses membres, mais un seul à la fois.

Concernant la définition, elle est identique à celle d’une structure ci ce n’est que le mot-clé union est employé en lieu et place de struct.

union type
{
    int entier;
    double flottant;
    void *pointeur;
    char lettre;
};

Le code ci-dessus défini une union type pouvant contenir un objet de type int ou de type double ou de type pointeur sur void ou de type char. Cette possiblité de ne stocker qu’un objet à la fois est traduite par le résultat de l’opérateur sizeof.

#include <stdio.h>

union type
{
    int entier;
    double flottant;
    void *pointeur;
    char lettre;
};


int main(void)
{
    printf("%u.\n", sizeof (union type));
    return 0; 
}
Résultat
8.

Dans notre cas, la taille de l’union correspond à la taille du plus grand type stocké à savoir les types void * et double qui font huit octets. Ceci traduit bien l’impossiblité de stocker plusieurs objets à la fois.

Notez que, comme les structures, les unions peuvent contenir des bits de bourrage, mais uniquement à leur fin.

Pour le surplus, une union s’utilise de la même manière qu’une structure et l’accès aux membres s’effectue à l’aide des opérateurs . et ->.

Utilisation

Étant donné leur singularité, les unions sont rarement employées. Leur principal intérêt est de réduire l’espace mémoire utilisé là où une structure ne le permet pas.

Par exemple, imaginez que vous souhaitiez construire une structure pouvant accueillir plusieurs types possibles, par exemple des entiers et des flottants. Vous aurez besoin de trois champs : un indiquant quel type est stocké dans la structure et deux permettant de stocker soit un entier soit un flottant.

struct nombre
{
    unsigned entier : 1;
    unsigned flottant : 1;
    int e;
    double f;
};

Toutefois, vous gaspillez ici de la mémoire puisque seul un des deux objets sera stocké. Une union est ici la bienvenue afin d’économiser de la mémoire.

struct nombre
{
    unsigned entier : 1;
    unsigned flottant : 1;
    union
    {
        int e;
        double f;
    } u;
};

Le code suivant illustre l’utilisation de cette construction.

#include <stdio.h>

struct nombre
{
    unsigned entier : 1;
    unsigned flottant : 1;
    union
    {
        int e;
        double f;
    } u;
};


static void affiche_nombre(struct nombre n)
{
    if (n.entier)
        printf("%d\n", n.u.e);
    else if (n.flottant)
        printf("%f\n", n.u.f);
}


int main(void)
{
    struct nombre a = { 0 };
    struct nombre b = { 0 };

    a.entier = 1;
    a.u.e = 10;
    b.flottant = 1;
    b.u.f = 10.56;

    affiche_nombre(a);
    affiche_nombre(b);
    return 0;
}
Résultat
10
10.560000

La syntaxe est toutefois un peu pénible puisqu’il est nécessaire d’employer deux fois l’opérateur . : une fois pour accéder aux membres de la structure et une seconde fois pour accéder aux membres de l’union. Par ailleurs, la nécessité d’intégrer l’union comme un champ de la structure, et donc de lui donner un nom, est également ennuyeux.

Heureusement pour nous, il est possible de rendre l’union « anonyme », c’est-à-dire de l’inclure comme champ de la structure, mais sans lui donner un nom. Dans un tel cas, les champs de l’union sont traîtés comme s’ils étaient des champs de la structure et sont donc accessibles comme tel.

#include <stdio.h>

struct nombre
{
    unsigned entier : 1;
    unsigned flottant : 1;
    union
    {
        int e;
        double f;
    };
};


static void affiche_nombre(struct nombre n)
{
    if (n.entier)
        printf("%d\n", n.e);
    else if (n.flottant)
        printf("%f\n", n.f);
}


int main(void)
{
    struct nombre a = { 0 };
    struct nombre b = { 0 };

    a.entier = 1;
    a.e = 10;
    b.flottant = 1;
    b.f = 10.56;

    affiche_nombre(a);
    affiche_nombre(b);
    return 0;
}

Notez que cette possibilité ne se limite pas aux unions, il est possible de faire de même avec des structures.


En résumé
  1. Une union est un agrégat regroupant des objets de types différents, mais ne pouvant en stocker qu’un seul à la fois ;
  2. Comme les structures, les unions peuvent comprendre des bits de bourrage, mais uniquement à leur fin ;
  3. Il est possible de simplifier l’accès aux champs d’une union lorsque celle-ci est elle-même un champ d’une autre structure ou d’une autre union en la rendant « anonyme ».