Frage

Ich haben verwendet die Gewerkschaften früher bequem;heute war ich bestürzt, als ich Lesen dieser Beitrag und kam zu wissen, dass dieser 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

tatsächlich Undefiniertes Verhalten I. e.das Lesen von einem Mitglied der union andere als die, die vor kurzem geschrieben, führt zu undefinierten Verhalten.Wenn dies nicht die beabsichtigte Verwendung der Gewerkschaften, was ist?Kann jemand bitte erklären es aufwendig?

Update:

Ich wollte klären ein paar Dinge im Nachhinein.

  • Die Antwort auf die Frage ist nicht die gleiche für C und C++;meine Ignoranten jüngeren selbst getaggt es als C-und C++.
  • Nach dem reinigen durch C++11 standard, konnte ich nicht abschließend sagen, dass es ruft den Zugriff auf/prüfen die nicht-aktiven Gewerkschaftsmitglied ist nicht definiert/keine Angabe/implementation-defined.Alle, die ich finden konnte, war §9.5/1:

    Wenn ein standard-layout union enthält mehrere standard-layout-Strukturen, die eine gemeinsame ersten Sequenz, und wenn ein Objekt dieses standard-layout-union-Typ enthält eine der standard-layout-Strukturen, ist es erlaubt zu überprüfen die gemeinsamen anfangs-Sequenz von beliebigen standard-layout struct Mitglieder.§9.2/19:Zwei standard-layout-Strukturen eine gemeinsame ersten Folge, wenn die entsprechenden Mitglieder layout-Typen kompatibel und entweder weder Mitglied ist ein bit-Feld oder beide bit-Felder mit der gleichen Breite für eine Sequenz von einem oder mehreren ersten Mitgliedern.

  • Während in C, (C99 TC3 - DR 283 ab) ist es legal zu tun (vielen Dank an Pascal Cuoq für das oben Holen).Jedoch, Versuch zu tun, es können noch führen zu undefiniertem Verhalten, wenn der Wert gelesen passiert, werden unwirksam (so genannte "trap-Repräsentation") für die Art ist Lesen durch.Andernfalls wird der Wert gelesen ist, ist die Implementierung definiert.
  • C89/90 nannte diese unter unspezifischen Verhalten (Annex J) und K&R-Buch sagt, es ist Implementierung definiert.Zitat von K&R:

    Dies ist der Zweck einer union - eine einzelne variable, die legitim halten, der eine von mehreren Arten.[...] so lange, wie die Auslastung ist konstant:die Art abgerufen werden müssen, die Art zuletzt gespeicherten.Es ist der Programmierer verantwortlich, um zu verfolgen, welcher Typ derzeit lagern in der union;die Ergebnisse sind von der jeweiligen Implementierung abhängig, wenn etwas gespeichert ist, als eine Art und extrahiert, wie der andere.

  • Auszug aus Stroustrup TC++PL (Hervorhebung von mir)

    Verwendung von Gewerkschaften entscheidend sein kann für compatness von Daten [...] manchmal missbraucht für "Typ-Konvertierung".

Vor allem diese Frage (deren Titel bleibt unverändert, da meine Fragen) gestellt wurde, mit dem Ziel, das Verständnis der Zweck der Gewerkschaften UND nicht auf das, was die Norm erlaubt E. g.Mit der Vererbung für die Wiederverwendung von code ist natürlich erlaubt, durch den C++ - standard, aber es war nicht die Absicht oder die ursprüngliche Absicht, die Einführung von Erbschafts-als C++ - Sprache-Funktion.Dies ist der Grund, Andrey Antwort weiterhin als offizieller.

War es hilfreich?

Lösung

Der Zweck der Gewerkschaften ist ziemlich offensichtlich, aber aus irgendeinem Grund es Menschen vermissen ziemlich oft.

Der Zweck der Vereinigung ist Speicher speichern durch den gleichen Speicherbereich unter Verwendung für verschiedene Objekte zu unterschiedlichen Zeiten zu speichern. Das ist es.

Es ist wie ein Zimmer in einem Hotel. Verschiedene Menschen leben in der es für Zeiträume nicht überlappend. Diese Menschen nie gerecht zu werden, und in der Regel wissen nichts voneinander. Durch geeignete Wahl des Time-Sharing der Räume Verwaltung (dh, indem sichergestellt werden verschiedene Leute zugewiesen bekommen nicht einen Raum zur gleichen Zeit), kann ein relativ kleines Hotel bietet Unterkünfte zu einer relativ großen Zahl von Menschen, die, was Hotels sind.

Das ist genau das, was Vereinigung der Fall ist. Wenn Sie wissen, dass mehrere Objekte in Ihrem Programm Haltewerte mit nicht überlappenden Mehrwert-Lebensdauern, dann können Sie „merge“ diese Objekte in eine Vereinigung und somit speichern Speicher. höchstens einen „aktive“ Mieter Genau wie ein Hotelzimmer in jedem Moment der Zeit, hat eine Vereinigung höchstens ein „aktives“ Mitglied in jedem Moment der Programmzeit. Es kann nur das „aktive“ Mitglied zu lesen. Durch das Schreiben in anderes Mitglied schalen Sie den Status „aktiv“ zu diesem anderen Mitglied.

Aus irgendeinem Grund diese ursprüngliche Zweck der Vereinigung habe „außer Kraft gesetzt“ mit etwas ganz anderes: ein Mitglied einer Gewerkschaft zu schreiben und sie dann durch ein anderes Mitglied inspizieren. Diese Art von Speicher Umdeutung (auch bekannt als „Typ punning“) ist keine gültige Verwendung von Gewerkschaften. Es führt in der Regel zu undefinierten Verhalten ist als die Herstellung Implementierung definiert Verhalten in C89 / 90 beschrieben.

EDIT: Mit Gewerkschaften für die Zwecke des Typs punning (dh ein Mitglied zu schreiben und dann noch lesen) eine detailliertere Definition in einem der Technische Berichtigungen zu dem C99-Standard gegeben wurde (siehe DR # 257 und DR # 283 ). Beachten Sie jedoch, dass formal bedeutet dies schützt Sie nicht aus in nicht definiertes Verhalten ausgeführt wird, indem versucht wird, eine Falle Darstellung zu lesen.

Andere Tipps

Sie könnten Gewerkschaften verwenden structs wie die folgenden zu schaffen, die ein Feld enthält, das uns sagt, welche Komponente der Union tatsächlich verwendet wird:

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

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

Das Verhalten aus der Sprache Sicht nicht definiert. Man bedenke, dass verschiedene Plattformen verschiedene Einschränkungen in Speicherausrichtung und endianness haben können. Der Code in einem Big-Endian im Vergleich zu einer Little-Endian-Maschine wird die Werte in der Struktur unterschiedlich aktualisieren. das Verhalten in der Sprache Fixing würde alle Implementierungen erfordert die gleiche endianness (und Speicherausrichtung Einschränkungen ...) die Einschränkung der Nutzung zu verwenden.

Wenn Sie C ++ verwenden (Sie verwenden zwei Tags) und Sie Portabilität wirklich interessieren, dann können Sie nur die Struktur verwenden und einen Setter bereitzustellen, der die uint32_t nimmt und setzt die Felder in geeigneter Weise durch bitmask Operationen. Das gleiche gilt in C mit einer Funktion durchgeführt werden.

Bearbeiten : Ich war AProgrammer erwartet eine Antwort auf Abstimmung aufzuschreiben und in der Nähe dieses. Da einige Kommentare haben darauf hingewiesen, endianness wird, indem man jede Implementierung in anderen Teilen der Norm behandelt entscheiden, was zu tun ist, und die Ausrichtung und Polsterung kann auch anders gehandhabt werden. Nun herrscht der strenge Aliasing, dass AProgrammer implizit bezieht sich hier ein wichtiger Punkt ist. Der Compiler darf Annahmen über die Änderung (oder das Fehlen der Modifikation) von Variablen. Im Fall der Union könnte der Compiler Befehle neu anordnen und die Lese jeder Farbkomponente über das Schreiben in die Farbvariable bewegen.

Die gemeinsam Verwendung von union stoße ich auf regelmäßig ist Aliasing .

Beachten Sie Folgendes:

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

Was macht das? Es ermöglicht sauber, ordentlich Zugriff eines Vector3f vec;-Mitglieder von entweder Name:

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

oder durch ganzzahlige Zugang in das Array

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

In einigen Fällen namentlich ist der Zugriff auf das deutlichste, was Sie tun können. In anderen Fällen, insbesondere, wenn die Achse programmgesteuert ausgewählt wird, zu tun, desto leichter ist es, die Achse durch numerischen Index für den Zugriff -. 0 für x, 1 für y, und z für 2

Wie Sie sagen, das ist streng Undefiniertes Verhalten, wenn es "funktioniert" auf vielen Plattformen.Der eigentliche Grund für die Verwendung von Gewerkschaften zu schaffen Variante records.

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;

Natürlich müssen Sie auch irgendeine Art von Unterscheidungsmerkmal zu sagen, was die Variante tatsächlich enthält.Und beachten Sie, dass in C++ die Gewerkschaften sind nicht viel, weil Sie nur POD-Typen - effektiv diejenigen ohne Konstruktoren und Destruktoren.

In C es war eine schöne Art und Weise so etwas wie eine Variante zu implementieren.

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;

In Zeiten der litlle Speicher diese Struktur mit weniger Speicher als eine Struktur, die alle Mitglied hat.

Übrigens C liefert

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

, um Bit-Werte.

Obwohl dies streng Verhalten nicht definiert ist, in der Praxis wird es mit so ziemlich jedem Compiler zu arbeiten. Es ist so eine weit verbreitete Paradigma, dass jeder sich selbst achtet Compiler „das Richtige“ in Fällen wie dies tun müssen. Es ist sicherlich über Typ-punning bevorzugt werden, die auch gebrochen Code mit einigen Compiler erzeugen kann.

In C ++, Variant Erhöhung eine sichere Umsetzung Version der Vereinigung, entworfen, um nicht definiertes Verhalten so weit wie möglich zu verhindern.

Seine Leistungen sind identisch mit dem enum + union Konstrukt (Stack zu vergeben usw.), aber es verwendet eine Vorlage Liste der Typen anstelle der enum:)

Das Verhalten nicht definiert werden, aber das bedeutet nur, es gibt keine „Standard“. Alle anständigen Compiler bieten #pragmas Verpackung steuern und die Ausrichtung, sondern kann verschiedene Standardwerte haben. Die Standardwerte werden auch verwendet, in Abhängigkeit von den Optimierungseinstellungen ändern.

Auch die Gewerkschaften sind nicht nur für platzsparend. Sie können moderne Compiler mit Typ punning helfen. Wenn Sie alles, was der Compiler reinterpret_cast<> kann keine Annahmen darüber, was Sie tun. Es kann werfen weg, was es über Ihre Art kennt und erneut starten (ein Schreib zurück in dem Speicher zu zwingen, die in diesen Tagen sehr ineffizient im Vergleich zu CPU-Taktrate).

technisch es nicht definiert, aber in Wirklichkeit die meisten (alle?) Compiler behandeln es genau das gleiche wie ein reinterpret_cast von einem Typ zum anderen verwendet wird, wobei das Ergebnis davon ist Implementierung definiert. Ich würde nicht verlieren Schlaf über Ihren aktuellen Code.

Für ein weiteres Beispiel für die tatsächliche Nutzung von Gewerkschaften, serialisiert die CORBA Framework Objekte der markierte Vereinigung Ansatz. Alle benutzerdefinierten Klassen sind Mitglieder einer (großen) Vereinigung und eine integer Kennung sagt die demarshaller, wie die Union zu interpretieren.

Andere haben die Architektur Unterschiede erwähnt (wenig - Big-Endian)

.

Ich lese das Problem, dass, da der Speicher für die Variablen gemeinsam genutzt wird, dann von einem Schreiben, die andere ändern und je nach ihrer Art, könnte der Wert bedeutungslos sein.

zB.     Union{       float f;       int i;     } X;

Schreiben auf x.i sinnlos wäre, wenn man dann von x.f gelesen -. Es sei denn, das ist, was Sie, um zu sehen, die Zeichen bestimmt, Exponenten oder Mantisse Komponenten des Schwimmers

Ich denke, es ist auch eine Frage der Ausrichtung. Wenn einige Variablen müssen Wort dann ausgerichtet werden Sie möglicherweise nicht das erwartete Ergebnis erhalten

zB.     Union{       char c [4];       int i;     } X;

Wenn hypothetisch auf einiger Maschine hatte ein char Wort c ausgerichtet sein, dann [0] und c [1] würde mit i Speicher teilen, aber nicht c [2] und c [3].

In der C-Sprache, wie es im Jahr 1974 dokumentiert wurde, wird alle Strukturelemente einen gemeinsamen Namensraum geteilt, und die Bedeutung von „ptr-> Mitglied“ wurde definiert wie das Hinzufügen der Mitglieds Verschiebung „PTR“ und die sich ergebende Adresse Zugriff auf die unter Verwendung von Mitglieds-Typ. Diese Konstruktion machte es möglich, die gleiche ptr mit Elemente zu verwenden, Namen aus verschiedenen Strukturdefinitionen genommen, aber mit dem gleichen Offset; Programmierer verwendeten diese Fähigkeit für eine Vielzahl von Zwecken.

Wenn Strukturelemente wurden ihre eigenen Namensraum zugewiesen, wurde es unmöglich zwei Strukturelemente mit der gleichen Verschiebung zu erklären. Hinzufügen von Gewerkschaften die Sprache machte es möglich, die gleiche Semantik zu erreichen, war in früheren Versionen der Sprache (wenn auch die Unfähigkeit zu haben Namen zu einem umschließenden Kontext exportiert haben nach wie vor mit einem erforderten Suchen / Ersetzen ersetzen foo-> Mitglied in foo-> type1.member). Was war wichtig war nicht so sehr, dass die Menschen, die Gewerkschaften haben hinzugefügt eine besondere Ziel Nutzung im Sinne, sondern vielmehr, dass sie bieten ein Mittel, mit dem Programmierer die auf den früheren Semantik, für welchen Zweck auch immer verlassen hatte, , sollte nach wie vor der Lage sein, die gleiche Semantik auch zu erreichen, wenn sie eine andere verwenden, hatte Syntax, es zu tun.

Sie können Verwendung a eine Vereinigung aus zwei Gründen:

  1. Eine praktische Möglichkeit, die gleichen Daten auf unterschiedliche Weise zugreifen können, wie in Ihrem Beispiel
  2. Ein Weg, um Platz zu sparen, wenn es verschiedene Datenelemente, von denen nur ein immer ‚aktiv‘ sein kann

1 ist wirklich mehr von einem C-Stil Hack short-cut Schreiben von Code auf der Basis Sie wissen, wie die Speicherarchitektur Arbeiten des Zielsystems. Wie bereits gesagt, Sie normalerweise mit ihm weg erhalten können, wenn Sie nicht tatsächlich viele verschiedene Plattformen zielen. Ich glaube, einige Compiler könnte lassen Sie Verpackungsrichtlinien verwenden auch (ich weiß, dass sie auf structs tun)?

Ein gutes Beispiel 2 kann in dem VARIANT verwendete Art ausführlich in COM.

Wie andere schon erwähnt, mit Aufzählungen kombiniert Gewerkschaften und eingewickelt in structs können verwendet werden, markiert Gewerkschaften zu implementieren. Eine praktische Anwendung ist Rust Result<T, E> zu implementieren, die ursprünglich implementiert wird unter Verwendung eines reinen enum (Rust kann zusätzliche Daten hält in Enumeration-Varianten). Hier ist ein C ++ Beispiel:

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;
}
Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top