Wann sollten Gewerkschaften eingesetzt werden? Warum brauchen wir sie?
236
Gewerkschaften werden häufig verwendet, um zwischen den binären Darstellungen von Ganzzahlen und Gleitkommazahlen zu konvertieren:
union
{
int i;
float f;
} u;
// Convert floating-point bits to integer:
u.f = 3.14159f;
printf("As integer: %08x\n", u.i);
Obwohl dies ein technisch undefiniertes Verhalten gemäß dem C-Standard ist (Sie sollten nur das zuletzt geschriebene Feld lesen), funktioniert es in praktisch jedem Compiler genau definiert.
Manchmal werden auch Gewerkschaften verwendet, um Pseudo-Polymorphismus in C zu implementieren, indem einer Struktur ein Tag zugewiesen wird, das angibt, welchen Objekttyp sie enthält, und dann die möglichen Typen zusammengeführt werden:
enum Type { INTS, FLOATS, DOUBLE };
struct S
{
Type s_type;
union
{
int s_ints[2];
float s_floats[2];
double s_double;
};
};
void do_something(struct S *s)
{
switch(s->s_type)
{
case INTS: // do something with s->s_ints
break;
case FLOATS: // do something with s->s_floats
break;
case DOUBLE: // do something with s->s_double
break;
}
}
Dies ermöglicht eine Größe von struct S
nur 12 Bytes anstelle von 28.
Gewerkschaften sind besonders nützlich bei der eingebetteten Programmierung oder in Situationen, in denen ein direkter Zugriff auf die Hardware / den Speicher erforderlich ist. Hier ist ein triviales Beispiel:
Dann können Sie wie folgt auf die Registrierung zugreifen:
Endianness (Bytereihenfolge) und Prozessorarchitektur sind natürlich wichtig.
Ein weiteres nützliches Feature ist der Bitmodifikator:
Mit diesem Code können Sie direkt auf ein einzelnes Bit in der Register- / Speicheradresse zugreifen:
quelle
Low-Level-Systemprogrammierung ist ein vernünftiges Beispiel.
IIRC, ich habe Gewerkschaften verwendet, um Hardwareregister in die Komponentenbits aufzuteilen. Sie können also auf ein 8-Bit-Register (wie es an dem Tag war, als ich dies getan habe ;-) in die Komponentenbits zugreifen.
(Ich vergesse die genaue Syntax, aber ...) Diese Struktur würde den Zugriff auf ein Steuerregister als Steuerbyte oder über die einzelnen Bits ermöglichen. Es wäre wichtig sicherzustellen, dass die Bits für eine gegebene Endianness auf die richtigen Registerbits abgebildet werden.
quelle
Ich habe es in einigen Bibliotheken als Ersatz für objektorientierte Vererbung gesehen.
Z.B
Wenn Sie möchten, dass die Verbindungsklasse eine der oben genannten Klassen ist, können Sie Folgendes schreiben:
Beispiel für die Verwendung in libinfinity: http://git.0x539.de/?p=infinote.git;a=blob;f=libinfinity/common/inf-session.c;h=3e887f0d63bd754c6b5ec232948027cbbf4d61fc;hb=HEAD#l74
quelle
Gewerkschaften ermöglichen es Datenmitgliedern, die sich gegenseitig ausschließen, denselben Speicher gemeinsam zu nutzen. Dies ist sehr wichtig, wenn der Speicher knapp ist, z. B. in eingebetteten Systemen.
Im folgenden Beispiel:
Diese Vereinigung nimmt den Raum eines einzelnen int ein und nicht von drei separaten int-Werten. Wenn der Benutzer den Wert von a und dann den Wert von b festlegt, wird der Wert von a überschrieben, da beide denselben Speicherort gemeinsam nutzen.
quelle
Viele Verwendungen. Einfach machen
grep union /usr/include/*
oder in ähnlichen Verzeichnissen. In den meisten Fällenunion
wird das in einstruct
und ein Mitglied der Struktur eingeschlossen, das angibt, auf welches Element in der Union zugegriffen werden soll. Zum Beispiel zur Kasseman elf
für reale Implementierungen.Dies ist das Grundprinzip:
quelle
Hier ist ein Beispiel für eine Vereinigung aus meiner eigenen Codebasis (aus dem Speicher und umschrieben, damit sie möglicherweise nicht genau ist). Es wurde verwendet, um Sprachelemente in einem von mir erstellten Interpreter zu speichern. Zum Beispiel der folgende Code:
besteht aus folgenden Sprachelementen:
Sprachelemente wurden wie folgt definiert
#define
:und die folgende Struktur wurde verwendet, um jedes Element zu speichern:
dann war die Größe jedes Elements die Größe der maximalen Vereinigung (4 Bytes für den Typ und 4 Bytes für die Vereinigung, obwohl dies typische Werte sind, die tatsächlichen hängen Größen von der Implementierung ab).
Um ein "Set" -Element zu erstellen, würden Sie Folgendes verwenden:
Um ein "variable [b]" - Element zu erstellen, verwenden Sie:
Um ein "Konstante [7]" - Element zu erstellen, verwenden Sie:
und Sie können es leicht erweitern, um float (
float flt
) oder rationals (struct ratnl {int num; int denom;}
) und andere Typen einzuschließen.Die Grundvoraussetzung ist, dass die
str
undval
nicht zusammenhängend im Speicher sind, sondern sich tatsächlich überlappen. Dies ist eine Möglichkeit, eine andere Ansicht auf denselben Speicherblock zu erhalten, wie hier dargestellt, wobei die Struktur auf dem Speicherort basiert0x1010
und Ganzzahlen und Zeiger beide sind 4 Bytes:Wenn es nur in einer Struktur wäre, würde es so aussehen:
quelle
make sure you free this later
Kommentar aus dem konstanten Element entfernt werden?Ich würde sagen, es macht es einfacher, Speicher wiederzuverwenden, der auf unterschiedliche Weise verwendet werden kann, dh Speicher zu sparen. Sie möchten beispielsweise eine "Varianten" -Struktur erstellen, mit der sowohl eine kurze Zeichenfolge als auch eine Zahl gespeichert werden können:
In einem 32-Bit-System würde dies dazu führen, dass für jede Instanz von mindestens 96 Bit oder 12 Bytes verwendet werden
variant
.Mit einer Union können Sie die Größe auf 64 Bit oder 8 Byte reduzieren:
Sie können noch mehr sparen, wenn Sie mehr verschiedene Variablentypen usw. hinzufügen möchten. Es kann sein, dass Sie ähnliche Dinge tun können, indem Sie einen ungültigen Zeiger werfen - aber die Vereinigung macht ihn sowohl zugänglicher als auch typischer sicher. Solche Einsparungen klingen nicht massiv, aber Sie sparen ein Drittel des für alle Instanzen dieser Struktur verwendeten Speichers.
quelle
Es ist schwierig, sich einen bestimmten Anlass vorzustellen, bei dem Sie diese Art von flexibler Struktur benötigen, möglicherweise in einem Nachrichtenprotokoll, in dem Sie Nachrichten unterschiedlicher Größe senden würden, aber selbst dann gibt es wahrscheinlich bessere und programmiererfreundlichere Alternativen.
Gewerkschaften sind ein bisschen wie Variantentypen in anderen Sprachen - sie können jeweils nur eine Sache enthalten, aber diese Sache kann ein Int, ein Float usw. sein, je nachdem, wie Sie es deklarieren.
Beispielsweise:
MyUnion enthält nur ein int ODER ein float, je nachdem, welches Sie zuletzt festgelegt haben . Also mach das:
u hält jetzt ein int gleich 10;
u hält jetzt einen Float von 1,0. Es enthält kein int mehr. Offensichtlich jetzt, wenn Sie versuchen, printf zu machen ("MyInt =% d", u.MyInt); dann werden Sie wahrscheinlich eine Fehlermeldung erhalten, obwohl ich mir über das spezifische Verhalten nicht sicher bin.
Die Größe der Union wird durch die Größe ihres größten Feldes bestimmt, in diesem Fall des Schwimmers.
quelle
sizeof(int) == sizeof(float)
(== 32
) normalerweise.Gewerkschaften werden verwendet, wenn Sie Strukturen modellieren möchten, die durch Hardware, Geräte oder Netzwerkprotokolle definiert sind, oder wenn Sie eine große Anzahl von Objekten erstellen und Platz sparen möchten. Sie brauchen sie jedoch in 95% der Fälle wirklich nicht. Halten Sie sich an den einfach zu debuggenden Code.
quelle
Viele dieser Antworten befassen sich mit dem Casting von einem Typ zum anderen. Gewerkschaften mit denselben Typen nutzen mich am meisten, nur mehr (dh beim Parsen eines seriellen Datenstroms). Sie ermöglichen es, das Parsen / Erstellen eines gerahmten Pakets trivial zu machen.
Bearbeiten Der Kommentar zu Endianness und Strukturauffüllung ist ein berechtigtes und großes Anliegen. Ich habe diesen Code fast ausschließlich in eingebetteter Software verwendet, von denen die meisten die Kontrolle über beide Enden der Pipe hatten.
quelle
Gewerkschaften sind großartig. Eine clevere Verwendung von Gewerkschaften, die ich gesehen habe, besteht darin, sie bei der Definition eines Ereignisses zu verwenden. Sie können beispielsweise festlegen, dass ein Ereignis 32 Bit umfasst.
Innerhalb dieser 32 Bits möchten Sie möglicherweise die ersten 8 Bits als Kennung des Absenders des Ereignisses festlegen ... Manchmal behandeln Sie das Ereignis als Ganzes, manchmal zerlegen Sie es und vergleichen seine Komponenten. Gewerkschaften geben Ihnen die Flexibilität, beides zu tun.
quelle
Was
VARIANT
ist damit in COM-Schnittstellen verwendet? Es hat zwei Felder - "Typ" und eine Vereinigung mit einem tatsächlichen Wert, der abhängig vom Feld "Typ" behandelt wird.quelle
In der Schule habe ich Gewerkschaften wie diese benutzt:
Ich habe es verwendet, um Farben einfacher zu handhaben, anstatt die Operatoren >> und << zu verwenden, musste ich nur den unterschiedlichen Index meines char-Arrays durchgehen.
quelle
Ich habe Union verwendet, als ich für eingebettete Geräte codiert habe. Ich habe C int, das 16 Bit lang ist. Und ich muss die höheren 8 Bits und die niedrigeren 8 Bits abrufen, wenn ich aus / speichern in das EEPROM lesen muss. Also habe ich diesen Weg benutzt:
Es muss nicht verschoben werden, damit der Code leichter zu lesen ist.
Auf der anderen Seite sah ich einen alten C ++ stl-Code, der union für den stl-Allokator verwendete. Wenn Sie interessiert sind, können Sie den sgi stl- Quellcode lesen . Hier ist ein Stück davon:
quelle
struct
um deinhigher
/ brauchenlower
? Im Moment sollten beide nur auf das erste Byte zeigen.Sehen Sie sich Folgendes an: X.25-Pufferbefehlsbehandlung
Einer der vielen möglichen X.25-Befehle wird in einem Puffer empfangen und mithilfe einer UNION aller möglichen Strukturen an Ort und Stelle verarbeitet.
quelle
In früheren Versionen von C würden alle Strukturdeklarationen einen gemeinsamen Satz von Feldern verwenden. Gegeben:
Ein Compiler würde im Wesentlichen eine Tabelle mit den Größen (und möglicherweise Ausrichtungen) der Strukturen und eine separate Tabelle mit den Namen, Typen und Offsets der Mitglieder der Strukturen erstellen. Der Compiler verfolgte nicht, welche Mitglieder zu welchen Strukturen gehörten, und erlaubte zwei Strukturen, nur dann ein Element mit demselben Namen zu haben, wenn Typ und Versatz übereinstimmten (wie bei Mitglied
q
vonstruct x
undstruct y
). Wenn p ein Zeiger auf einen Strukturtyp wäre, würde p-> q den Versatz von "q" zum Zeiger p hinzufügen und ein "int" von der resultierenden Adresse abrufen.Angesichts der obigen Semantik war es möglich, eine Funktion zu schreiben, die einige nützliche Operationen für mehrere Arten von Strukturen austauschbar ausführen konnte, vorausgesetzt, dass alle von der Funktion verwendeten Felder mit nützlichen Feldern innerhalb der fraglichen Strukturen ausgerichtet waren. Dies war eine nützliche Funktion, und das Ändern von C zur Validierung der für den Strukturzugriff verwendeten Elemente anhand der Typen der betreffenden Strukturen hätte den Verlust bedeutet, wenn keine Struktur vorhanden wäre, die mehrere benannte Felder an derselben Adresse enthalten kann. Das Hinzufügen von "Union" -Typen zu C hat dazu beigetragen, diese Lücke etwas zu schließen (allerdings nicht, IMHO, so wie es hätte sein sollen).
Ein wesentlicher Teil der Fähigkeit der Gewerkschaften, diese Lücke zu schließen, war die Tatsache, dass ein Zeiger auf ein Gewerkschaftsmitglied in einen Zeiger auf eine Gewerkschaft umgewandelt werden konnte, die dieses Mitglied enthält, und ein Zeiger auf eine Gewerkschaft in einen Zeiger auf ein Mitglied umgewandelt werden konnte. Während die Standard - C89 nicht ausdrücklich sagte , dass ein Casting
T*
direkt an einU*
entsprach es auf einen Zeiger auf jede Vereinigung Typ Gießen beide enthältT
undU
, und dann Gießen dassU*
kein definiertes Verhalten der letzteren Guss Sequenz durch die betroffen wären Der verwendete Unionstyp und der Standard haben keine entgegengesetzte Semantik für eine direkte Umwandlung vonT
bis angegebenU
. In Fällen, in denen eine Funktion einen Zeiger unbekannter Herkunft empfangen hat, ist das Verhalten des Schreibens eines Objekts überT*
Konvertieren desT*
zu aU*
, und dann das Lesen des Objekts überU*
wäre gleichbedeutend mit dem Schreiben einer Vereinigung über ein Mitglied vom TypT
und dem Lesen als TypU
, was in einigen Fällen standarddefiniert (z. B. beim Zugriff auf Mitglieder der allgemeinen Anfangssequenz) und implementierungsdefiniert (eher) wäre als undefiniert) für den Rest. Während es für Programme selten war, die GUS-Garantien mit tatsächlichen Objekten vom Typ Union zu nutzen, war es weitaus üblicher, die Tatsache auszunutzen, dass Zeiger auf Objekte unbekannter Herkunft sich wie Zeiger auf Gewerkschaftsmitglieder verhalten mussten und die damit verbundenen Verhaltensgarantien hatten.quelle
foo
einint
Offset mit 8 ist,anyPointer->foo = 1234;
bedeutet dies "Nehmen Sie die Adresse in anyPointer, verschieben Sie sie um 8 Bytes und führen Sie einen Integer-Speicher mit dem Wert 1234 für die resultierende Adresse durch. Der Compiler muss nicht wissen oder sich darum kümmern, ob eranyPointer
identifiziert wird." jeder Strukturtyp, derfoo
unter seinen Mitgliedern aufgeführt war.anyPointer
mit einem Strukturelement identifiziert, wie überprüft der Compiler dann diesen Zustandto have a member with the same name only if the type and offset matched
Ihres Beitrags?p->foo
vom Typ und Offset von abhängen würdefoo
. Im Wesentlichenp->foo
war Abkürzung für*(typeOfFoo*)((unsigned char*)p + offsetOfFoo)
. Wenn ein Compiler auf eine Strukturelementdefinition stößt, muss für Ihre letztere Frage entweder kein Mitglied mit diesem Namen vorhanden sein oder das Mitglied mit diesem Namen muss denselben Typ und Versatz haben. Ich würde vermuten, dass das gequietscht hätte, wenn eine nicht übereinstimmende Strukturelementdefinition existiert hätte, aber ich weiß nicht, wie es mit Fehlern umgegangen ist.Ein einfaches und sehr nützliches Beispiel ist ....
Vorstellen:
Sie haben ein
uint32_t array[2]
und möchten auf das 3. und 4. Byte der Bytekette zugreifen. du könntest tun*((uint16_t*) &array[1])
. Dies verstößt jedoch leider gegen die strengen Aliasing-Regeln!Mit bekannten Compilern können Sie jedoch Folgendes tun:
technisch ist dies immer noch ein Verstoß gegen die Regeln. Alle bekannten Standards unterstützen diese Verwendung.
quelle