Question

Je l'ai utilisé plus tôt les syndicats confortablement; aujourd'hui, j'étais inquiet quand je lis ce post et est venu de savoir que ce code

union ARGB
{
    uint32_t colour;

    struct componentsTag
    {
        uint8_t b;
        uint8_t g;
        uint8_t r;
        uint8_t a;
    } components;

} pixel;

pixel.colour = 0xff040201;  // ARGB::colour is the active member from now on

// somewhere down the line, without any edit to pixel

if(pixel.components.a)      // accessing the non-active member ARGB::components

est un comportement non défini réellement à savoir la lecture d'un membre du syndicat autre que celui récemment écrit conduit à un comportement non défini. Si ce n'est pas ce que l'utilisation prévue des syndicats, est? Quelqu'un peut s'il vous plaît expliquer minutieusement?

Mise à jour:

Je voulais clarifier quelques petites choses avec le recul.

  • La réponse à la question n'est pas la même chose pour C et C ++; mon plus jeune ignorant soi marqué à la fois comme C et C ++.
  • Après avoir écumé par la norme C ++ 11 Je ne saurais dire de façon concluante qu'il appelle l'accès / l'inspection d'un membre d'un syndicat non actif est indéfini / Non spécifié / mise en œuvre définie. Tout ce que je pouvais trouver était §9.5 / 1:
      

    Si une union standard mise en page contient plusieurs struct standard mise en page qui partagent une séquence initiale commune, et si un objet de ce type d'union standard mise en page contient l'un des struct standard mise en page, il est permis d'inspecter la première commune séquence selon l'une quelconque des membres de la structure standard mise en page. §9.2 / 19: Deux structs standard layout partager une séquence initiale commune si les membres correspondants ont des types de mise en page compatible et non plus ni membre est un champ binaire ou les deux sont des champs de bits avec la même largeur pour une séquence d'un ou plusieurs initial membres.

  • Alors que dans C, ( C99 TC3 - DR 283 partir), il est légal de le faire ( grâce à Pascal Cuoq avoir soulevé cette question). Cependant, en essayant de le faire il peut encore conduire à un comportement non défini , si la valeur lue se trouve être invalide (appelé « représentation piège ») pour le type il est lu à travers. Dans le cas contraire, la valeur lue est mise en œuvre définie.
  • C89 / 90 a appelé ce sous un comportement non spécifié (Annexe J) et le livre de K & R dit qu'il est défini par l'implémentation. Citation de K & R:

      

    Ceci est le but d'une union - une seule variable qui peut contenir légitimement une d'un de plusieurs types. [...] tant que l'utilisation est cohérente: le type récupéré doit être le type le plus récemment enregistré. Il est de la responsabilité du programmeur de garder une trace de quel type est actuellement stocké dans une union; les résultats dépendent de la mise en œuvre si quelque chose est stocké sous forme d'un type et extrait comme un autre.

  • Extrait du TC ++ PL (Souligné par) de Stroustrup

      

    Utilisation des syndicats peut être essentiel pour compatness des données [...] parfois mal utilisés pour "conversion de type ".

Et surtout, cette question (dont le titre reste inchangé depuis mon SKO) a été posé avec l'intention de comprendre le but des syndicats et non sur ce que la norme permet Par ex Utilisation de l'héritage pour la réutilisation de code est, bien sûr, permis par le C ++ standard, mais

Était-ce utile?

La solution

Le but des syndicats est assez évident, mais pour une raison que les gens manquent assez souvent.

L'objectif de l'Union est pour sauvegarder la mémoire en utilisant la même région de mémoire pour stocker des objets différents à des moments différents. Ca y est.

Il est comme une chambre dans un hôtel. Différentes personnes vivent pour des périodes de non-chevauchement des temps. Ces personnes ne se rencontrent jamais, et en général ne savent rien les uns des autres. En gérant bien le temps de partage des chambres (à savoir en veillant à différentes personnes ne sont pas affectés à une chambre en même temps), un hôtel relativement petit peut fournir des logements à un nombre relativement important de personnes, ce qui est ce que les hôtels sont pour.

C'est exactement ce que l'union fait. Si vous savez que plusieurs objets dans votre programme ont des valeurs avec de valeur des durées de vie ne se chevauchent pas, alors vous pouvez ces « fusionner » les objets dans une union et donc économiser de la mémoire. Tout comme une chambre d'hôtel a au plus un membre « actif » locataire à chaque moment du temps, une union a au plus un « actif » à chaque moment de la durée du programme. Seul le membre « actif » peut être lu. En écrivant dans un autre membre, vous changez l'état « actif » à cet autre membre.

Pour une raison quelconque, cet objectif initial de syndicalistes se « shunté » avec quelque chose de complètement différent: écrire un membre d'un syndicat, puis l'inspecter par un autre membre. Ce genre de réinterprétation de la mémoire (alias « type calembour ») est pas une utilisation valable des syndicats. Il conduit généralement à un comportement non défini est décrit comme produisant un comportement défini par l'implémentation dans C89 / 90.

EDIT: Utiliser les syndicats aux fins de type calembour (par exemple l'écriture d'un membre, puis lecture autre) a été donné une définition plus détaillée dans l'un des errata techniques à la norme C99 (voir DR # 257 et DR # 283 ). Cependant, gardez à l'esprit que cela ne vous protège pas formellement de courir dans un comportement non défini en essayant de lire une représentation piège.

Autres conseils

Vous pouvez utiliser les syndicats pour créer struct comme suit, qui contient un champ qui nous indique quel composant de l'union est en fait utilisé:

struct VAROBJECT
{
    enum o_t { Int, Double, String } objectType;

    union
    {
        int intValue;
        double dblValue;
        char *strValue;
    } value;
} object;

Le comportement est indéfini du point de vue linguistique. Considérez que les différentes plates-formes peuvent avoir des contraintes dans l'alignement de la mémoire et boutisme. Le code dans un grand endian par rapport à une petite machine endian mettra à jour les valeurs de la struct différemment. Fixation du comportement dans la langue, il faudrait toutes les implémentations d'utiliser le même boutisme (et les contraintes d'alignement de mémoire ...) limitant l'utilisation.

Si vous utilisez C ++ (vous utilisez deux balises) et vous vraiment à la portabilité, vous pouvez simplement utiliser la struct et fournir un setter qui prend la uint32_t et définit les champs de manière appropriée par des opérations de bitmask. La même chose peut être fait en C avec une fonction.

Modifier : Je me attendais AProgrammer d'écrire une réponse à voter et fermer celui-ci. Comme certains commentaires ont souligné, boutisme est traitée dans d'autres parties de la norme en laissant chaque mise en œuvre de décider quoi faire, et l'alignement et le rembourrage peuvent également être traités différemment. Maintenant, les règles strictes d'aliasing AProgrammer fait référence implicitement sont un point important. Le compilateur est autorisé à faire des hypothèses sur la modification (ou l'absence de modification) des variables. Dans le cas de l'union, le compilateur pourrait modifier l'ordre des instructions et déplacer la lecture de chaque composante de couleur sur l'écriture à la variable de couleur.

La plupart commune utilisation de union Je viens régulièrement à travers aliasing .

Considérez ce qui suit:

union Vector3f
{
  struct{ float x,y,z ; } ;
  float elts[3];
}

Qu'est-ce que cela fait? Il permet un accès propre, soigné des membres d'un Vector3f vec; par soit name:

vec.x=vec.y=vec.z=1.f ;

ou par un accès entier dans la gamme

for( int i = 0 ; i < 3 ; i++ )
  vec.elts[i]=1.f;

Dans certains cas, l'accès par nom est la chose la plus claire que vous pouvez faire. Dans d'autres cas, en particulier lorsque l'axe est choisie par programme, la chose plus facile à faire est d'accéder à l'axe par index numérique -. 0 pour x, 1 pour y, et z 2 pour

Comme vous le dites, cela est strictement un comportement non défini, mais il va « travailler » sur de nombreuses plateformes. La vraie raison de l'utilisation des syndicats est de créer des enregistrements de variantes.

union A {
   int i;
   double d;
};

A a[10];    // records in "a" can be either ints or doubles 
a[0].i = 42;
a[1].d = 1.23;

Bien sûr, vous avez aussi besoin d'une sorte de discriminateur de dire ce que la variante contient en fait. Et noter que dans C ++ syndicats ne sont pas une grande utilité, car ils ne peuvent contenir que les types POD -. Efficacement sans les constructeurs et destructeurs

En C, il était une belle façon de mettre en œuvre quelque chose comme une variante.

enum possibleTypes{
  eInt,
  eDouble,
  eChar
}


struct Value{

    union Value {
      int iVal_;
      double dval;
      char cVal;
    } value_;
    possibleTypes discriminator_;
} 

switch(val.discriminator_)
{
  case eInt: val.value_.iVal_; break;

En temps de mémoire litlle cette structure est en utilisant moins de mémoire que d'une structure qui possède tout l'élément.

Par la voie C fournit

    typedef struct {
      unsigned int mantissa_low:32;      //mantissa
      unsigned int mantissa_high:20;
      unsigned int exponent:11;         //exponent
      unsigned int sign:1;
    } realVal;

pour accéder aux valeurs de bits.

Bien que ce soit strictement un comportement non défini, dans la pratique, il travaillera avec à peu près tout compilateur. Il est un paradigme largement utilisé que tout compilateur qui se respecte devra faire « la bonne chose » dans des cas comme celui-ci. Il est certainement préférable à type calembour, qui pourrait bien générer du code cassé avec des compilateurs.

En C ++, Boost Variant mettre en œuvre un coffre-fort version du syndicat, conçu pour empêcher un comportement non défini, autant que possible.

Ses performances sont identiques à la construction enum + union (pile des alloué trop, etc.), mais il utilise une liste de modèles de types au lieu de la enum:)

Le comportement peut être indéfini, mais cela signifie simplement qu'il n'y a pas un « standard ». Tous les compilateurs décents offrent #pragmas le contrôle du conditionnement et de l'alignement, mais peut avoir différentes valeurs par défaut. Les valeurs par défaut changent également en fonction des paramètres d'optimisation utilisés.

En outre, les syndicats ne sont pas juste pour un gain de place. Ils peuvent aider les compilateurs modernes de type calembour. Si vous reinterpret_cast<> tout ce que le compilateur ne peut pas faire des hypothèses sur ce que vous faites. Il peut avoir à jeter ce qu'il sait au sujet de votre type et recommencer (forcer une écriture de retour à la mémoire, ce qui est très inefficace ces jours-ci par rapport à la vitesse d'horloge du processeur).

Techniquement, il est non défini, mais en réalité, la plupart (tous?) Compilateurs traitent exactement la même chose que l'aide d'un reinterpret_cast d'un type à l'autre, dont le résultat est la mise en œuvre définie. Je ne perdrai pas le sommeil de votre code actuel.

Pour un exemple de l'utilisation réelle des syndicats, le cadre de CORBA sérialise des objets en utilisant l'approche syndicale étiquetée. Toutes les classes définies par l'utilisateur sont membres d'un (énorme) union, et un indique la demarshaller comment interpréter l'union.

D'autres ont mentionné les différences d'architecture (petits - big endian).

Je lis le problème que depuis la mémoire pour les variables est partagée, puis en écrivant à l'un, les autres changent et, en fonction de leur type, la valeur peut être vide de sens.

par exemple.     syndicat{       float f;       int i;     } X;

L'écriture à x.i serait vide de sens si vous alors lu x.f -. Sauf si cela est ce que vous aviez l'intention afin de regarder le signe, exposant ou composants mantisse du flotteur

Je pense qu'il ya aussi un problème d'alignement: Si certaines variables doivent être alignées mot, alors vous ne pouvez pas obtenir le résultat escompté

.

par exemple.     syndicat{       char c [4];       int i;     } X;

Si, hypothétiquement, sur une machine char devait être aligné mot alors c [0] et c [1] partageraient stockage avec i mais pas c [2] et c [3].

Dans le langage C comme il a été documenté en 1974, tous les membres de la structure partagent un espace de noms commun, et le sens de « ptr-> membre » a été défini ajouter la Le déplacement de membre à « PTR » et l'accès à l'adresse résultante en utilisant la type de membre. Cette conception a permis d'utiliser le même ptr avec un membre Les noms tirés de différentes définitions de structure, mais avec le même décalage; programmeurs utilisés pour cette capacité à diverses fins.

Lorsque les membres de la structure ont été assignés leurs propres espaces de noms, il est devenu impossible déclarer deux éléments de structure avec le même déplacement. Ajout de syndicats à la langue a permis d'atteindre la même sémantique qui avait été disponible dans les versions antérieures de la langue (bien que l'incapacité d'avoir Les noms exportés vers un contexte englobante peut-être encore rendue nécessaire en utilisant un trouver / remplacer pour remplacer foo-> membre dans foo-> type1.member). ce qui était important était pas tant que les gens qui ont ajouté les syndicats ont tout particulier l'utilisation à l'esprit la cible, mais plutôt qu'ils fournissent un moyen par lequel les programmeurs qui avait compté sur la sémantique antérieure, pour quelque fin , devrait encore être en mesure d'atteindre la même sémantique, même si elles devaient utiliser un autre syntaxe pour le faire.

Vous pouvez utiliser a une union pour deux raisons principales:

  1. Une façon pratique d'accéder aux mêmes données de différentes façons, comme dans votre exemple
  2. Une façon d'économiser de l'espace quand il y a différents membres de données qui ne peut jamais être « actif »

1 Est-ce vraiment plus d'un hack C-style à l'écriture de code raccourci sur la base que vous savez comment fonctionne l'architecture mémoire du système cible. Comme il a déjà dit que vous pouvez normalement sortir avec elle si vous ne cible pas vraiment beaucoup de différentes plates-formes. Je crois que certains compilateurs pourraient vous permettre d'utiliser les directives d'emballage aussi (je sais qu'ils font sur struct)?

Un bon exemple de 2. se trouve dans le VARIANT type utilisé largement dans COM.

Comme d'autres ont mentionné, les syndicats combinés avec énumérations et enveloppés dans struct peuvent être utilisés pour mettre en œuvre les syndicats marqués. Une utilisation pratique consiste à mettre en œuvre la Result<T, E> de Rust, qui est à l'origine mis en œuvre au moyen d'un enum pur (Rust peut contenir des données supplémentaires dans les variantes d'énumération). Voici un exemple de C de:

template <typename T, typename E> struct Result {
    public:
    enum class Success : uint8_t { Ok, Err };
    Result(T val) {
        m_success = Success::Ok;
        m_value.ok = val;
    }
    Result(E val) {
        m_success = Success::Err;
        m_value.err = val;
    }
    inline bool operator==(const Result& other) {
        return other.m_success == this->m_success;
    }
    inline bool operator!=(const Result& other) {
        return other.m_success != this->m_success;
    }
    inline T expect(const char* errorMsg) {
        if (m_success == Success::Err) throw errorMsg;
        else return m_value.ok;
    }
    inline bool is_ok() {
        return m_success == Success::Ok;
    }
    inline bool is_err() {
        return m_success == Success::Err;
    }
    inline const T* ok() {
        if (is_ok()) return m_value.ok;
        else return nullptr;
    }
    inline const T* err() {
        if (is_err()) return m_value.err;
        else return nullptr;
    }

    // Other methods from https://doc.rust-lang.org/std/result/enum.Result.html

    private:
    Success m_success;
    union _val_t { T ok; E err; } m_value;
}
Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top