Puis-je atsortingbuer une valeur à un membre de l’union et lire la même valeur d’un autre?

En gros, j’ai un

struct foo { /* variable denoting active member of union */ enum whichmember w; union { struct some_struct my_struct; struct some_struct2 my_struct2; struct some_struct3 my_struct3; /* let's say that my_struct is the largest member */ }; }; main() { /*...*/ /* earlier in main, we get some struct foo d with an */ /* unknown union assignment; dw is correct, however */ struct foo f; f.my_struct = d.my_struct; /* mystruct isn't necessarily the */ /* active member, but is the biggest */ fw = dw; /* code that determines which member is active through fw */ /* ... */ /* we then access the *correct* member that we just found */ /* say, f.my_struct3 */ f.my_struct3.some_member_not_in_mystruct = /* something */; } 

Accéder aux membres du syndicat C via des pointeurs semble indiquer que l’access aux membres via des pointeurs est acceptable. Voir les commentaires.

Mais ma question concerne leur access direct . Fondamentalement, si j’écris toutes les informations dont j’ai besoin sur le membre le plus important de l’union et que je garde la trace des types manuellement, l’access au membre spécifié manuellement générera-t-il toujours les informations correctes à chaque fois?

    Je remarque que le code de la question utilise une union anonyme, ce qui signifie qu’il doit être écrit pour C11; les syndicats anonymes ne faisaient pas partie de C90 ou de C99.

    ISO / IEC 9899: 2011 , la norme actuelle C11, dit ceci:

    §6.5.2.3 Structure et membres du syndicat

    ¶3 Une expression postfixée suivie du . opérateur et un identifiant désigne un membre d’une structure ou d’un object d’union. La valeur est celle du membre nommé, 95) et est une lvalue si la première expression est une lvalue. Si la première expression a un type qualifié, le résultat a la version qualifiée du type du membre désigné.

    ¶4 Une expression de suffixe suivie de l’opérateur -> et d’un identifiant désigne un membre d’une structure ou d’un object d’union. La valeur est celle du membre nommé de l’object sur lequel pointe la première expression et est une lvalue. 96) Si la première expression est un pointeur sur un type qualifié, le résultat a la version qualifiée du type du membre désigné.

    ¶5…

    ¶6 Une garantie spéciale est donnée afin de simplifier l’utilisation des unions: si une union contient plusieurs structures partageant une séquence initiale commune (voir ci-dessous), et si l’object union contient actuellement l’une de ces structures, il est autorisé à inspecter: la partie initiale commune de l’un d’entre eux où qu’une déclaration du type complet de l’union soit visible. Deux structures partagent une séquence initiale commune si les membres correspondants ont des types compatibles (et, pour les champs de bits, les mêmes largeurs) pour une séquence d’un ou de plusieurs membres initiaux.


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

    96) Si &E est une expression de pointeur valide (où & est l’opérateur ” adresse-de ” qui génère un pointeur sur son opérande), l’expression (&E)->MOS est identique à E.MOS .

    Italique comme dans la norme

    Et la section §6.2.6 Représentations de types dit (en partie):

    §6.2.6.1 Généralités

    ¶6 Lorsqu’une valeur est stockée dans un object de type structure ou union, y compris dans un object membre, les octets de la représentation de l’object correspondant aux octets de remplissage prennent des valeurs non spécifiées. 51) La valeur d’une structure ou d’un object d’union n’est jamais une représentation de piège, même si la valeur d’un membre de la structure ou d’un object d’union peut être une représentation de piège.

    ¶7 Lorsqu’une valeur est stockée dans un membre d’un object de type union, les octets de la représentation d’object qui ne correspondent pas à ce membre mais correspondent à d’autres membres prennent des valeurs non spécifiées.


    51) Ainsi, par exemple, l’affectation de structure n’a pas besoin de copier de bits de remplissage.


    Mon interprétation de ce que vous faites est que la note de bas de page 51 dit “cela pourrait ne pas fonctionner” parce que vous n’avez assigné qu’une partie de la structure. Au mieux, vous marchez sur de la glace mince. Toutefois, vous stipulez que la structure affectée (dans l’ f.my_struct = d.my_struct; ) est le membre le plus grand. Les chances sont modérément élevées que cela ne se passe pas bien, mais si les octets de remplissage des deux structures (dans le membre actif du syndicat et dans le membre le plus important du syndicat) se trouvent à des endroits différents, les choses pourraient mal tourner et si vous avez signalé un problème à l’auteur du compilateur, celui-ci vous dira simplement “ne contrevenez pas à la norme”.

    Donc, dans la mesure où je suis un juriste spécialiste des langues, la réponse de cet avocat est: “Ce n’est pas garanti”. En pratique, il est peu probable que vous renconsortingez des problèmes, mais la possibilité existe et vous ne pouvez revenir en arrière.

    Pour sécuriser votre code, utilisez simplement f = d; avec une mission syndicale.


    Exemple illustratif

    Supposons que la machine nécessite double alignement double sur une limite de 8 octets et sizeof(double) == 8 , que int doit être aligné sur une limite de 4 octets et sizeof(int) == 4 et que le short doit être aligné sur un Limite de 2 octets et sizeof(short) == 2 ). Il s’agit d’un ensemble plausible et même commun de tailles et d’alignements.

    De plus, supposons que vous ayez une variante d’union à deux structures de la structure dans la question:

     struct Type_A { char x; double y; }; struct Type_B { int a; short b; short c; }; enum whichmember { TYPE_A, TYPE_B }; struct foo { enum whichmember w; union { struct Type_A s1; struct Type_B s2; }; }; 

    Désormais, dans les tailles et les alignements spécifiés, la struct Type_A occupera 16 octets et la struct Type_B , 8 octets, de sorte que l’union utilisera également 16 octets. La disposition de l’union sera comme ceci:

     +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | x | p...a...d...d...i...n...g | y | s1 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | a | b | c | p...a...d...d...i...n...g | s2 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 

    L’élément w signifierait également qu’il y a 8 octets dans struct foo avant l’union (anonyme), dont il est probable que w n’en occupe que 4. La taille de struct foo est donc de 24 sur cette machine. Ce n’est pas particulièrement pertinent pour la discussion, cependant.

    Supposons maintenant que nous ayons un code comme celui-ci:

     struct foo d; dw = TYPE_B; d.s2.a = 1234; d.s2.b = 56; d.s2.c = 78; struct foo f; f.s1 = d.s1; fw = TYPE_B; 

    Maintenant, en vertu de la décision de la note de bas de page 51, l’affectation de structure f.s1 = d.s1; ne doit pas copier les bits de remplissage. Je ne connais aucun compilateur qui se comporte de la sorte, mais la norme stipule qu’un compilateur n’a pas besoin de copier les bits de remplissage. Cela signifie que la valeur de f.s1 pourrait être:

     +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | x | g...a...r...b...a...g...e | r...u...b...b...i...s...h | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 

    La poubelle est due au fait que ces 7 octets n’ont pas besoin d’être copiés (la note de bas de page 51 indique qu’il s’agit d’une option, même s’il est peu probable que ce soit une option exercée par un compilateur actuel). La foutaise est parce que l’initialisation de d ne définit jamais de valeur dans ces octets; le contenu de cette partie de la structure n’est pas spécifié.

    Si vous continuez maintenant et essayez de traiter f comme une copie de d , vous serez peut-être un peu surpris de constater que seul 1 octet des 8 octets pertinents de f.s2 est réellement initialisé.

    Je vais souligner de nouveau: je ne connais aucun compilateur qui ferait cela. Mais la question étant étiquetée «juriste de langue», le problème est donc «qu’est-ce que la norme de langue énonce?» Et voici mon interprétation des sections citées de la norme.

    Oui, votre code fonctionnera car avec une union, le compilateur partagera le même espace mémoire pour tous les éléments.

    Par exemple, si: & f.mystruct = 100 alors & f.mystruct2 = 100 et & f.mystruct3 = 100

    Si Mystruct est le plus important, il fonctionnera tout le temps.

    Oui, vous pouvez y accéder directement. Vous pouvez atsortingbuer une valeur à un membre du syndicat et la lire via un autre membre du syndicat. Le résultat sera déterministe et correct.