139 votes

L'accès inactif membre de l'union indéfinis?

J'étais sous l'impression que l'accès à un union membre autre que le dernier set est UB, mais je n'arrive pas à trouver une référence solide (autres que de réponses affirmant qu'il est UB mais sans le soutien de la norme).

Donc, est-ce un comportement indéfini?

150voto

ecatmur Points 64173

La confusion est que C permet explicitement de type beaucoup les jeux de mots par l'intermédiaire d'un syndicat, alors que C++ () n'a pas une telle autorisation.

6.5.2.3 de la Structure et des membres de l'union

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

La situation avec C++:

9.5 Syndicats [classe.l'union]

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

C++ plus tard a la langue qui permet l'utilisation de syndicats contenant structs avec la commune de séquences initiales; ce n'est cependant pas permis de type beaucoup les jeux de mots.

Pour déterminer si le type d'union-beaucoup les jeux de mots est autorisé en C++, nous avons cherché plus loin. Rappelons que est une référence normative pour le C++11 (et C99 a similaires la langue de C11 permettant à l'union de type beaucoup les jeux de mots):

3.9 Types [de base.types]

4 - L'objet de la représentation d'un objet de type T est la séquence de N unsigned char objets pris par l'objet de type T, où N est égal à sizeof(T). La valeur de la représentation d'un objet est l'ensemble de bits la valeur de type T. Pour trivialement copiable types, la valeur de la représentation est un ensemble de bits dans l'objet la représentation qui détermine une valeur, qui est un élément discret de la mise en œuvre définies par l'ensemble de des valeurs. 42
42) L'intention est que le modèle de mémoire de C++ est compatible avec celui de la norme ISO/IEC 9899 Langage de Programmation C.

Il devient particulièrement intéressant lorsque nous lisons

3.8 durée de vie des Objets [de base.la vie]

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

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

3.9.2 les types Composés [de base.composé]

Si un objet de type T est situé à une adresse à l'Un, un pointeur de type cv T* dont la valeur est la adresse A est le point de l'objet, indépendamment de la façon dont la valeur a été obtenue.

En supposant que l'opération qui nous intéresse est de type beaucoup les jeux de mots c'est à dire en prenant la valeur de un non-actif membre de l'union, et vu ci-dessus que nous avons une référence valide à l'objet visé par ce membre, cette opération est lvalue-à-rvalue de conversion:

4.1 Lvalue-à-rvalue de conversion [conv.lval]

Un glvalue d'un non-fonctionnement, à la non-type de tableau T peut être converti en prvalue. Si T est un type incomplète, un programme qui nécessite cette conversion est mal formé. Si l'objet pour lequel la glvalue se réfère 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 est non initialisée, un programme qui nécessite cette conversion a un comportement indéfini.

La question est alors de savoir si un objet qui est un non-actif membre de l'union est initialisé par le stockage de l'actif membre de l'union. Aussi loin que je peux dire, ce n'est pas le cas et si bien que si:

  • un syndicat est copié en char de la matrice de stockage et à l'arrière (3.9:2), ou
  • un syndicat est bytewise copié à l'autre de l'union du même type (3.9:3), ou
  • un syndicat est accessible à travers les limites du langage par un élément de programme conforme à la norme ISO/IEC 9899 (comme défini) (3.9:4 note 42), puis

l'accès à une union par un non-membre actif est défini et est défini de manière à suivre l'objet et la valeur de la représentation, de l'accès sans que l'un des ci-dessus interpositions est un comportement indéterminé. Ceci a des implications pour les optimisations autorisées à être exécutées sur un tel programme, comme la mise en œuvre peut bien sûr supposer que les comportements indéfinis ne se produit pas.

Bien que l'on peut légitimement forme une lvalue à un non-actif membre de l'union (c'est pourquoi l'affectation à un non-membre actif sans la construction est ok), il est considéré comme non initialisée.

30voto

Bo Persson Points 42821

Le C++11 standard le dit de cette façon

9.5 les Syndicats

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

Si une seule valeur est stockée, comment pouvez-vous lire un autre? Il n'est pas là.


La gcc documentation listes en vertu de cette mise en Œuvre, les comportements définis

  • Un membre d'un syndicat objet est accessible à l'aide d'un membre d'un autre type (C90 6.3.2.3).

Pertinentes octets de la représentation de l'objet sont traités comme un objet du type utilisé pour l'accès. Voir Type beaucoup les jeux de mots. Cela peut être un piège de la représentation.

ce qui indique que ce n'est pas requise par la norme.

19voto

Jerry Coffin Points 237758

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

Une garantie spéciale est faite dans le but de simplifier l'utilisation des syndicats: si un syndicat contient plusieurs structures qui partagent une même séquence initiale (voir ci-dessous), et si l'union objet contient actuellement l'une de ces structures, il est autorisé à inspecter la commune première partie de l'un d'eux partout où une déclaration complète du type de l'union est visible. Deux structures partagent une séquence initiale si les membres correspondants ont des types compatibles (et, pour peu que les champs, de la même largeur) pour une séquence d'un ou de plusieurs les premiers membres.

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

Si un modèle de mise en page de l'union contient deux ou plus de standard-layout des structures qui partagent une même séquence initiale, et si le standard-layout de l'union de l'objet contient actuellement l'un de ces standard-layout structures, il est permis pour inspecter la commune de la partie initiale de l'un d'eux. Deux standard-layout structures partagent un tronc commun initial la séquence si l'un des membres correspondants ont mise en page des types compatibles et soit ni membre est un peu de champ ou les deux sont des bits de champs avec la même largeur pour une séquence d'un ou de plusieurs membres initiaux.

Bien que ni les états directement, ces deux portent une implication forte que "l'inspection" (lecture) un membre est "autorisé" seulement si 1) c'est (en partie) le membre le plus récemment écrit, ou 2) fait partie d'une commune de la séquence initiale.

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

12voto

mpu Points 218

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

Noter que le type de regroupement ne comprennent 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 clairement implique que vous ne devez pas écrire dans un membre et de le lire dans un autre. Dans ce cas, il pourrait être un comportement non défini par manque de précision.

2voto

En C++, nous avons actuellement défectueux règles pour la durée de vie des objets, ce qui nous empêche de bien dire que c'est OK ou pas OK conformément à la Norme. Pour le démontrer, la Norme dit qu'un objet de type T devient vivante, si

  • de stockage avec le bon alignement et la taille pour le type T est obtenu, et
  • si l'objet est non-trivial d'initialisation, de son initialisation est terminée

La deuxième balle n'est jamais active pour les types comme int. Ce qui suit est que le suivant a un comportement indéfini

int *p = malloc(sizeof(int) + sizeof(float));
// between these lines, there is already both at least an int and a float object!
*p = 10;
int x = *p;

Pourquoi? Parce que vous êtes la lecture d'un float objet par l'utilisation d'un int lvalue - ce n'est pas autorisé par le repliement des règles. Cette incohérentes, illogique définition de la durée de vie des objets est actuellement essayé d'être fixé par http://www.open-std.org/jtc1/sc22/wg21/docs/cwg_active.html#1116 mais jusqu'à ce que nous avons une saine définition, on ne peut pas dire quelque chose de précis.

Dans la pratique, je crois que les règles sont grandement simplifiées (qui signifie: - contient beaucoup de détails manquants nécessaires pour être utilisé dans la pratique), interprété de la façon suivante

Un objet de la non-type de classe T devient vivante, si

  • de stockage avec le bon alignement et la taille pour le type T est obtenu, et
    • le stockage est une nouvelle expression, variable ou un objet non-union de données définition du membre, ou qui a été créée pour un objet temporaire, ou si ce n'est qu',
    • une écriture par une lvalue de type T de cet emplacement a été fait

La dernière balle aurait besoin pour gérer écrit par un volatile T, lorsque l'objet est non-volatile - l'objet est ensuite devenu trop volatile? Et aussi des besoins pour gérer écrit par const T* lvalues d' T* objets, et encore quelques cas particuliers - nous ne voulons pas changer le type de l'emplacement cible!

Ce mécanisme prévoit les règles qui l' unions peuvent travailler. Si vous écrivez dans un membre, nous apportons un objet du type de la donnée membre vivant. Si le type de l'autre membre de données est aliasing compatible, on peut encore lire que de l'objet par l'utilisation des autres membres de données de type, à mon avis, parce que je ne trouve nulle part dans la Norme qui interdit explicitement (la phrase que l'union peut stocker la valeur d'un seul membre à la fois n'est pas une règle - que la situation est la même pour tous les autres de la plaine variable - et encore, nous pouvons appliquer le repliement de la règle à tous!).

union A {
  int *p;
  const int *q;
};

int main() {
  int x = 10;
  A y = { &x };
  int *r = y.q; // valid per 3.10p10bullet3
}

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X