web-dev-qa-db-fra.com

Accès à un membre du syndicat inactif et comportement indéfini?

J'avais l'impression que l'accès à un membre union autre que le dernier ensemble est UB, mais je n'arrive pas à trouver une référence solide (autre que les réponses affirmant que c'est UB mais sans aucun support de la norme) .

Alors, est-ce un comportement indéfini?

108
Luchian Grigore

La confusion est que C autorise explicitement la punition de type par le biais d'une union, tandis que C++ ( c ++ 11 ) n'a pas une telle autorisation.

c11

6.5.2.3 Structure et membres du syndicat

95) Si le membre utilisé pour lire le contenu d'un objet union n'est pas le même que le dernier membre utilisé pour stocker une valeur dans l'objet, la partie appropriée de la représentation d'objet de la valeur est réinterprétée en tant que représentation d'objet dans le nouveau type comme décrit au 6.2.6 (un processus parfois appelé "type punning"). Cela pourrait être une représentation piège.

La situation avec C++:

c ++ 11

9.5 Unions [class.union]

Dans une union, au plus l'un des membres de données non statiques peut être actif à tout moment, c'est-à-dire que la valeur d'au plus un des membres de données non statiques peut être stockée dans une union à tout moment.

C++ a plus tard un langage permettant l'utilisation d'unions contenant structs avec des séquences initiales communes; cela ne permet cependant pas de punition de type.

Pour déterminer si l'union de type punning est est autorisée en C++, nous devons chercher plus loin. Rappelons que c99 est une référence normative pour C++ 11 (et C99 a un langage similaire à C11 autorisant le type-punning d'union):

3.9 Types [basic.types]

4 - La représentation objet d'un objet de type T est la séquence de N objets char non signés repris par l'objet de type T, où N est égal à sizeof (T). La représentation de la valeur d'un objet est l'ensemble de bits qui contient la valeur de type T.Pour les types trivialement copiables, la représentation de la valeur est un ensemble de bits dans la représentation de l'objet qui détermine une valeur, qui est un élément discret d'une implémentation- ensemble de valeurs dé fi ni. 42
42) L'objectif est que le modèle de mémoire de C++ soit compatible avec celui du langage de programmation C. ISO/CEI 9899.

Ça devient particulièrement intéressant quand on lit

3.8 Durée de vie des objets [basic.life]

La durée de vie d'un objet de type T commence lorsque: - un stockage avec l'alignement et la taille appropriés pour le type T est obtenu, et - si l'objet a une initialisation non triviale, son initialisation est terminée.

Ainsi, pour un type primitif (qui ipso facto a une initialisation triviale) contenu dans une union, la durée de vie de l'objet englobe au moins la durée de vie de l'union elle-même. Cela nous permet d'invoquer

3.9.2 Types de composés [basic.compound]

Si un objet de type T se trouve à une adresse A, un pointeur de type cv T * dont la valeur est l'adresse A est dit pointer vers cet objet, quelle que soit la façon dont la valeur a été obtenue.

En supposant que l'opération qui nous intéresse est une punition de type, c'est-à-dire en prenant la valeur d'un membre d'union non actif, et étant donné ce qui précède que nous avons une référence valide à l'objet auquel ce membre fait référence, cette opération est lvalue-to -rvalue conversion:

4.1 Conversion Lvalue-to-rvalue [conv.lval]

Une glvalue d'un type non fonction et non tableau T peut être convertie en valeur pr. Si T est un type incomplet, un programme qui nécessite cette conversion est mal formé. Si l'objet auquel la glvalue fait référence n'est pas un objet de type T et n'est pas un objet d'un type dérivé de T, ou si l'objet n'est pas initialisé, un programme qui nécessite cette conversion a un comportement indéfini.

La question est alors de savoir si un objet qui est un membre d'union non actif est initialisé par stockage sur le membre d'union actif. Pour autant que je sache, ce n'est pas le cas et donc si:

  • une union est copiée dans le stockage de tableau char et inversement (3.9: 2), ou
  • une union est copiée par octet dans une autre union du même type (3.9: 3), ou
  • une union est accessible au-delà des frontières linguistiques par un élément de programme conforme à ISO/IEC 9899 (pour autant que cela soit défini) (3.9: 4 note 42), puis

l'accès à une union par un membre non actif est défini et est défini pour suivre la représentation de l'objet et de la valeur, l'accès sans l'une des interpositions ci-dessus est un comportement indéfini. Cela a des implications pour les optimisations autorisées à effectuer sur un tel programme, car la mise en œuvre peut bien sûr supposer qu'aucun comportement indéfini ne se produit.

Autrement dit, bien que nous puissions légitimement former une valeur à un membre d'union non actif (c'est pourquoi l'affectation à un membre non actif sans construction est correcte), il est considéré comme non initialisé.

119
ecatmur

La norme C++ 11 le dit ainsi

9.5 Unions

Dans une union, au plus l'un des membres de données non statiques peut être actif à tout moment, c'est-à-dire que la valeur d'au plus un des membres de données non statiques peut être stockée dans une union à tout moment.

Si une seule valeur est stockée, comment pouvez-vous en lire une autre? Ce n'est tout simplement pas là.


La documentation de gcc le répertorie sous Comportement défini par l'implémentation

  • Un membre d'un objet union est accessible en utilisant un membre d'un type différent (C90 6.3.2.3).

Les octets pertinents de la représentation de l'objet sont traités comme un objet du type utilisé pour l'accès. Voir Punification de type. Cela peut être une représentation piège.

indiquant que cela n'est pas requis par la norme C.


2016-01-05: Grâce aux commentaires, j'ai été lié à C99 Defect Report # 28 qui ajoute un texte similaire en tant que note de bas de page au document standard C:

78a) Si le membre utilisé pour accéder au contenu d'un objet union n'est pas le même que le dernier membre utilisé pour stocker une valeur dans l'objet, la partie appropriée de la représentation d'objet de la valeur est réinterprétée en tant que représentation d'objet dans le nouveau type comme décrit en 6.2.6 (un processus parfois appelé "type punning"). Cela pourrait être une représentation piège.

Je ne sais pas si cela clarifie beaucoup, étant donné qu'une note de bas de page n'est pas normative pour la norme.

26
Bo Persson

Je pense que la norme la plus proche de dire que c'est un comportement indéfini est où elle définit le comportement d'une union contenant une séquence initiale commune (C99, §6.5.2.3/5):

Une garantie spéciale est faite afin de simplifier l'utilisation des unions: si une union contient plusieurs structures qui partagent une séquence initiale commune (voir ci-dessous), et si l'objet union contient actuellement une de ces structures, il est autorisé d'inspecter la commune partie initiale de l'un d'entre eux partout où une déclaration du type complet de l'union est visible. Deux structures partagent une séquence initiale commune si les membres correspondants ont des types compatibles (et, pour les champs binaires, les mêmes largeurs) pour une séquence d'un ou plusieurs membres initiaux.

C++ 11 donne des exigences/autorisations similaires au §9.2/19:

Si une union de mise en page standard contient deux ou plusieurs structures de mise en page standard qui partagent une séquence initiale commune, et si l'objet d'union de mise en page standard contient actuellement l'une de ces structures de mise en page standard, il est autorisé d'inspecter la partie initiale commune de tout d'eux. Deux structures de mise en page standard partagent une séquence initiale commune si les membres correspondants ont des types compatibles avec la mise en page et qu'aucun des membres n'est un champ de bits ou les deux sont des champs de bits de même largeur pour une séquence d'un ou plusieurs membres initiaux.

Bien que ni l'un ni l'autre ne le dise directement, ces deux éléments impliquent fortement que "inspecter" (lire) un membre est "autorisé" seulement si 1) c'est (une partie de) le membre le plus récemment écrit, ou 2 ) fait partie d'une séquence initiale commune.

Ce n'est pas une déclaration directe que faire autrement est un comportement indéfini, mais c'est le plus proche dont je suis au courant.

17
Jerry Coffin

Quelque chose qui n'est pas encore mentionné par les réponses disponibles est la note de bas de page 37 du paragraphe 21 de la section 6.2.5:

Notez que le type d'agrégat n'inclut pas le type d'union car un objet avec le type d'union ne peut contenir qu'un seul membre à la fois.

Cette exigence semble impliquer clairement que vous ne devez pas écrire dans un membre et lire dans un autre. Dans ce cas, il peut s'agir d'un comportement non défini par manque de spécification.

11
mpu