web-dev-qa-db-fra.com

taille d'une union en C/C++

Quelle est la taille de l'union en C/C++? Est-ce la taille du plus gros type de données qu'il contient? Si tel est le cas, comment le compilateur calcule-t-il comment déplacer le pointeur de pile si l'un des types de données plus petits de l'union est actif?

37
Naveen

La norme répond à toutes les questions de la section 9.5 de la norme C++ ou de la section 6.5.2.3, paragraphe 5, de la norme C99 (ou du paragraphe 6 de la norme C11):

Dans une union, au plus un des membres de données peut être actif à tout moment, c'est-à-dire que la valeur d'au plus un des membres de données peut être stockée dans une union à tout moment. [Remarque: une garantie spéciale est fournie afin de simplifier l'utilisation des unions: si une union POD contient plusieurs structures POD qui partagent une séquence initiale commune (9.2) et si un objet de ce type d'union POD contient l'une des suivantes: les structures POD, il est autorisé à inspecter la séquence initiale commune à l’un des membres de la structure POD; voir 9.2. ] La taille d'une union est suffisante pour contenir le plus grand de ses membres de données. Chaque membre de données est alloué comme s'il s'agissait du seul membre d'une structure. 

Cela signifie que chaque membre partage la même région de mémoire. Il y a est au plus un membre actif, mais vous ne pouvez pas savoir lequel. Vous devrez stocker vous-même ces informations sur le membre actuellement actif. Stocker un tel drapeau en plus de l’union (par exemple, avoir une structure avec un entier comme type-indicateur et une union comme magasin de données) vous donnera une "union discriminée": Une union qui sait dans quel type c'est actuellement le "actif".

Une utilisation courante est dans les lexers, où vous pouvez avoir différents jetons, mais en fonction du jeton, vous avez différentes informations à stocker (mettre line dans chaque structure pour montrer ce qu'est une séquence initiale commune):

struct tokeni {
    int token; /* type tag */
    union {
        struct { int line; } noVal;
        struct { int line; int val; } intVal;
        struct { int line; struct string val; } stringVal;
    } data;
};

La norme vous permet d’accéder à line de chaque membre, car c’est la séquence initiale commune de chacun. 

Il existe des extensions de compilateur permettant d'accéder à tous les membres sans tenir compte de celui dont la valeur est actuellement stockée. Cela permet une réinterprétation efficace des bits stockés avec différents types parmi chacun des membres. Par exemple, les éléments suivants peuvent être utilisés pour disséquer une variable float en 2 shorts non signés:

union float_cast { unsigned short s[2]; float f; };

Cela peut être très pratique pour écrire du code de bas niveau. Si le compilateur ne supporte pas cette extension, mais que vous le faites quand même, vous écrivez du code dont les résultats ne sont pas définis. Assurez-vous donc que votre compilateur prend en charge cette fonctionnalité si vous utilisez cette astuce.

28

Une union prend toujours autant de place que le membre le plus grand. Peu importe ce qui est actuellement utilisé.

union {
  short x;
  int y;
  long long z;
}

Une instance de la variable union ci-dessus prendra toujours au moins un long long pour le stockage.

Note latérale: Comme l'a noté Stefano , l'espace réel que n'importe quel type (union, struct, class) prendra dépendra d'autres problèmes tels que l'alignement par le compilateur. Je ne suis pas passé par là pour des raisons de simplicité, je voulais simplement dire qu’un syndicat prend en compte le point le plus important. Il est important de savoir que la taille réelle ne dépend de l'alignement.

54
Mehrdad Afshari

Cela dépend du compilateur et des options.

int main() {
  union {
    char all[13];
    int foo;
  } record;

printf("%d\n",sizeof(record.all));
printf("%d\n",sizeof(record.foo));
printf("%d\n",sizeof(record));

}

Cela génère:

13 4 16

Si je me souviens bien, cela dépend de l'alignement que le compilateur place dans l'espace alloué. Donc, à moins que vous n'utilisiez une option spéciale, le compilateur mettra du rembourrage dans votre espace union.

edit: avec gcc vous devez utiliser une directive pragma

int main() {
#pragma pack(Push, 1)
      union {
           char all[13];
           int foo;
      } record;
#pragma pack(pop)

      printf("%d\n",sizeof(record.all));
      printf("%d\n",sizeof(record.foo));
      printf("%d\n",sizeof(record));

}

cette sortie

13 4 13

Vous pouvez également le voir depuis le désassemblage (quelques impressions supprimées, pour plus de clarté)

  0x00001fd2 <main+0>:    Push   %ebp             |  0x00001fd2 <main+0>:    Push   %ebp
  0x00001fd3 <main+1>:    mov    %esp,%ebp        |  0x00001fd3 <main+1>:    mov    %esp,%ebp
  0x00001fd5 <main+3>:    Push   %ebx             |  0x00001fd5 <main+3>:    Push   %ebx
  0x00001fd6 <main+4>:    sub    $0x24,%esp       |  0x00001fd6 <main+4>:    sub    $0x24,%esp
  0x00001fd9 <main+7>:    call   0x1fde <main+12> |  0x00001fd9 <main+7>:    call   0x1fde <main+12>
  0x00001fde <main+12>:   pop    %ebx             |  0x00001fde <main+12>:   pop    %ebx
  0x00001fdf <main+13>:   movl   $0xd,0x4(%esp)   |  0x00001fdf <main+13>:   movl   $0x10,0x4(%esp)                                         
  0x00001fe7 <main+21>:   lea    0x1d(%ebx),%eax  |  0x00001fe7 <main+21>:   lea    0x1d(%ebx),%eax
  0x00001fed <main+27>:   mov    %eax,(%esp)      |  0x00001fed <main+27>:   mov    %eax,(%esp)
  0x00001ff0 <main+30>:   call  0x3005 <printf>   |  0x00001ff0 <main+30>:   call   0x3005 <printf>
  0x00001ff5 <main+35>:   add    $0x24,%esp       |  0x00001ff5 <main+35>:   add    $0x24,%esp
  0x00001ff8 <main+38>:   pop    %ebx             |  0x00001ff8 <main+38>:   pop    %ebx
  0x00001ff9 <main+39>:   leave                   |  0x00001ff9 <main+39>:   leave
  0x00001ffa <main+40>:   ret                     |  0x00001ffa <main+40>:   ret    

Où la seule différence est dans main + 13, où le compilateur alloue sur la pile 0xd au lieu de 0x10

15
Stefano Borini

Il n'y a pas de notion de type de données actif pour une union. Vous êtes libre de lire et d'écrire n'importe quel "membre" du syndicat: c'est à vous d'interpréter ce que vous recevez.

Par conséquent, la taille d'une union est toujours la taille de son plus grand type de données.

10
mouviciel

La taille sera au moins celle du type le plus grand. Il n'y a pas de concept de type "actif".

3
anon

Vous devriez vraiment considérer une union comme un conteneur pour le type de données le plus important qu'elle contient associé à un raccourci pour une distribution. Lorsque vous utilisez l'un des membres les plus petits, l'espace inutilisé est toujours là, mais il reste simplement inutilisé. 

Vous le voyez souvent utilisé en combinaison avec des appels ioctl () sous Unix, tous les appels ioctl () passent par la même structure, qui contient une union de toutes les réponses possibles. Par exemple. Cet exemple provient de /usr/include/linux/if.h et cette structure est utilisée dans les ioctl () pour configurer/interroger l'état d'une interface Ethernet, les paramètres de la requête définissent quelle partie de l'union est réellement utilisé:

struct ifreq 
{
#define IFHWADDRLEN 6
    union
    {
        char    ifrn_name[IFNAMSIZ];        /* if name, e.g. "en0" */
    } ifr_ifrn;

    union {
        struct  sockaddr ifru_addr;
        struct  sockaddr ifru_dstaddr;
        struct  sockaddr ifru_broadaddr;
        struct  sockaddr ifru_netmask;
        struct  sockaddr ifru_hwaddr;
        short   ifru_flags;
        int ifru_ivalue;
        int ifru_mtu;
        struct  ifmap ifru_map;
        char    ifru_slave[IFNAMSIZ];   /* Just fits the size */
        char    ifru_newname[IFNAMSIZ];
        void *  ifru_data;
        struct  if_settings ifru_settings;
    } ifr_ifru;
};
2
amo-ej1
  1. La taille du plus grand membre.

  2. C'est pourquoi les unions ont généralement du sens dans une structure qui a un drapeau qui indique quel est le membre "actif".

Exemple:

struct ONE_OF_MANY {
    enum FLAG { FLAG_SHORT, FLAG_INT, FLAG_LONG_LONG } flag;
    union { short x; int y; long long z; };
};
0
pyon

Quelle est la taille de l'union en C/C++? Est-ce la taille du plus grand type de données à l'intérieur?

Oui , La taille de l'union est la taille de son plus grand membre.

Par exemple : 

#include<stdio.h>

union un
{
    char c;
    int i;
    float f;
    double d;
};

int main()
{
    union un u1;
    printf("sizeof union u1 : %ld\n",sizeof(u1));
    return 0;
}

Sortie:

sizeof union u1 : 8
sizeof double d : 8

Ici, le membre le plus important est double. Les deux ont la taille 8. Donc, comme sizeof vous l'a bien dit, la taille de l'union est bien 8.

comment le compilateur calcule-t-il comment déplacer le pointeur de pile, le cas échéant du plus petit type de données de l'union est-il actif?

Il gère en interne par le compilateur. Supposons que nous accédions à l'un des données membre de l'union, nous ne pouvons pas accéder à un autre membre de données, car nous pouvons accéder à un seul membre de données de l'union car chaque membre de données partage la même mémoire. En utilisant Union, nous pouvons économiser beaucoup d’espace précieux.

0
M.S Chaudhari