340 Stimmen

Zweck von Unions in C und C++

Ich habe Gewerkschaften früher bequem genutzt; heute war ich alarmiert, als ich las diese Stelle und erfuhr, 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

ist eigentlich undefiniertes Verhalten, d.h. das Lesen aus einem anderen Mitglied der Union als dem, in das gerade geschrieben wurde, führt zu undefiniertem Verhalten. Wenn dies nicht die beabsichtigte Verwendung von Unions ist, was dann? Kann mir das bitte jemand genauer erklären?

Aktualisierung:

Ich wollte im Nachhinein ein paar Dinge klarstellen.

  • Die Antwort auf diese Frage ist für C und C++ nicht dieselbe; mein unwissendes jüngeres Ich hat sie sowohl als C als auch als C++ bezeichnet.

  • Nachdem ich den C++11-Standard durchforstet hatte, konnte ich nicht schlüssig sagen, dass der Zugriff auf ein nicht aktives Unionsmitglied undefiniert/unspezifisch/implementierungsdefiniert ist. Alles, was ich finden konnte, war §9.5/1:

    Wenn eine Standard-Layout-Union mehrere Standard-Layout-Strukturen enthält, die eine gemeinsame Anfangssequenz haben, und wenn ein Objekt dieses Standard-Layout-Union-Typs eine der Standard-Layout-Strukturen enthält, ist es erlaubt, die gemeinsame Anfangssequenz eines beliebigen Standard-Layout-Struktur-Mitglieds zu untersuchen. §9.2/19: Zwei Standard-Layout-Strukturen haben eine gemeinsame Anfangssequenz, wenn die entsprechenden Mitglieder layout-kompatible Typen haben und entweder keines der beiden Mitglieder ein Bit-Feld ist oder beide Bit-Felder mit der gleichen Breite für eine Sequenz von einem oder mehreren Anfangsmitgliedern sind.

  • Während in C, ( C99 TC3 - DR 283 ab), ist es legal, dies zu tun ( Dank an Pascal Cuoq für die Erwähnung dieses Themas). Der Versuch, dies zu tun, ist jedoch es kann immer noch zu undefiniertem Verhalten führen wenn der gelesene Wert für den Typ, über den er gelesen wird, ungültig ist (so genannte "Trap-Darstellung"). Andernfalls ist der gelesene Wert durch die Implementierung definiert.

  • In C89/90 wurde dies unter "unspecified behavior" (Annex J) aufgeführt, und in K&Rs Buch heißt es, es sei "implementation defined". Zitat aus K&R:

    Dies ist der Zweck einer Vereinigung - eine einzelne Variable, die legitimerweise einen von mehreren Typen enthalten kann. [...] solange die Verwendung konsistent ist: der Typ, der abgerufen wird, muss der Typ sein, der zuletzt gespeichert wurde. Es liegt in der Verantwortung des Programmierers, den Überblick darüber zu behalten, welcher Typ gerade in einer Union gespeichert ist; die Ergebnisse sind von der Implementierung abhängig, wenn etwas als ein Typ gespeichert und als ein anderer extrahiert wird.

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

    Die Verwendung von Unions kann für die Kompatibilität von Daten wesentlich sein [...] manchmal fälschlicherweise für "Typumwandlung" verwendet ".

Vor allem wurde diese Frage (deren Titel seit meiner Anfrage unverändert geblieben ist) in der Absicht gestellt, den Zweck von Gewerkschaften zu verstehen UND nicht, was die Norm erlaubt Die Verwendung von Vererbung zur Wiederverwendung von Code ist natürlich nach dem C++-Standard zulässig, aber es war nicht der Zweck oder die ursprüngliche Absicht der Einführung der Vererbung als ein Merkmal der Sprache C++ . Dies ist der Grund dafür, dass die Antwort von Andrey weiterhin die akzeptierte Antwort ist.

572voto

AnT Punkte 300728

Der Zweck von Gewerkschaften ist ziemlich offensichtlich, aber aus irgendeinem Grund wird er oft übersehen.

Der Zweck der Gewerkschaft ist um Speicherplatz zu sparen durch die Verwendung desselben Speicherbereichs für die Speicherung verschiedener Objekte zu unterschiedlichen Zeiten. Das war's.

Es ist wie ein Zimmer in einem Hotel. In ihm leben verschiedene Menschen für sich nicht überschneidende Zeiträume. Diese Menschen treffen sich nie und wissen im Allgemeinen nichts voneinander. Indem man die zeitliche Aufteilung der Zimmer richtig verwaltet (d. h. indem man sicherstellt, dass nicht verschiedene Personen zur gleichen Zeit in einem Zimmer untergebracht werden), kann ein relativ kleines Hotel einer relativ großen Anzahl von Personen eine Unterkunft bieten, wofür Hotels ja da sind.

Das ist genau das, was die Gewerkschaft tut. Wenn Sie wissen, dass mehrere Objekte in Ihrem Programm Werte mit sich nicht überschneidenden Werte-Lebensdauern enthalten, können Sie diese Objekte zu einer Vereinigung "verschmelzen" und so Speicherplatz sparen. Genauso wie ein Hotelzimmer zu jedem Zeitpunkt höchstens einen "aktiven" Mieter hat, hat eine Union zu jedem Zeitpunkt der Programmzeit höchstens ein "aktives" Mitglied. Nur das "aktive" Mitglied kann gelesen werden. Indem Sie in ein anderes Mitglied schreiben, wechseln Sie den "aktiven" Status zu diesem anderen Mitglied.

Aus irgendeinem Grund wurde dieser ursprüngliche Zweck der Union durch etwas völlig anderes "außer Kraft gesetzt": ein Mitglied einer Union zu schreiben und es dann durch ein anderes Mitglied zu inspizieren. Diese Art der Neuinterpretation des Speichers (auch bekannt als "type punning") ist keine gültige Verwendung von Gewerkschaften. Sie führt im Allgemeinen zu undefiniertem Verhalten wird in C89/90 als ein durch die Implementierung definiertes Verhalten beschrieben.

EDIT: Die Verwendung von Unions für die Zwecke des Typ-Punings (d.h. das Schreiben eines Elements und das anschließende Lesen eines anderen) wurde in einem der Technical Corrigenda zum C99-Standard genauer definiert (siehe DR#257 y DR#283 ). Beachten Sie jedoch, dass dies formal nicht davor schützt, beim Versuch, eine Trap-Darstellung zu lesen, auf undefiniertes Verhalten zu stoßen.

53voto

Erich Kitzmueller Punkte 35336

Sie könnten Unions verwenden, um Strukturen wie die folgende zu erstellen, 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;

34voto

Das Verhalten ist vom Standpunkt der Sprache aus gesehen undefiniert. Bedenken Sie, dass verschiedene Plattformen unterschiedliche Einschränkungen bei der Speicherausrichtung und Endianness haben können. Der Code auf einem Big-Endian- und einem Little-Endian-Rechner wird die Werte in der Struktur unterschiedlich aktualisieren. Eine Fixierung des Verhaltens in der Sprache würde erfordern, dass alle Implementierungen die gleiche Endianness (und Speicherausrichtungsbeschränkungen...) verwenden, was die Verwendung einschränkt.

Wenn Sie C++ verwenden (Sie verwenden zwei Tags) und Ihnen die Portabilität wirklich wichtig ist, können Sie einfach die Struktur verwenden und einen Setter bereitstellen, der die uint32_t und setzt die Felder entsprechend durch Bitmaskenoperationen. Das Gleiche kann in C mit einer Funktion gemacht werden.

bearbeiten : Ich hatte erwartet, dass AProgrammer eine Antwort auf die Abstimmung verfasst und diese schließt. Wie in einigen Kommentaren hervorgehoben wurde, wird die Endianness in anderen Teilen des Standards behandelt, indem jeder Implementierung die Entscheidung überlassen wird, was zu tun ist, und Alignment und Padding können ebenfalls unterschiedlich gehandhabt werden. Die strengen Aliasing-Regeln, auf die sich AProgrammer implizit bezieht, sind hier ein wichtiger Punkt. Der Compiler darf Annahmen über die Änderung (oder Nichtänderung) von Variablen treffen. Im Fall der Vereinigung könnte der Compiler die Anweisungen neu anordnen und das Lesen jeder Farbkomponente über das Schreiben in die Farbvariable stellen.

32voto

bobobobo Punkte 61051

Die meisten gemeinsame Verwendung von union Ich stoße regelmäßig auf die Aliasing .

Bedenken Sie Folgendes:

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

Was bewirkt das? Es ermöglicht einen sauberen, ordentlichen Zugang zu einem Vector3f vec; Mitglieder von entweder Name:

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

oder durch ganzzahligen Zugriff auf das Array

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

In manchen Fällen ist der Zugriff über den Namen das eindeutigste, was Sie tun können. In anderen Fällen, insbesondere wenn die Achse programmatisch ausgewählt wird, ist es einfacher, auf die Achse über einen numerischen Index zuzugreifen - 0 für x, 1 für y und 2 für z.

10voto

Wie Sie sagen, ist dies ein absolut undefiniertes Verhalten, obwohl es auf vielen Plattformen "funktionieren" wird. Der eigentliche Grund für die Verwendung von Unions ist die Erstellung von Variantendatensätzen.

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 brauchen Sie auch eine Art Unterscheidungsmerkmal, das angibt, was die Variante tatsächlich enthält. Und beachten Sie, dass Unions in C++ nicht sehr nützlich sind, da sie nur POD-Typen enthalten können - also solche ohne Konstruktoren und Destruktoren.

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X