Antworten:
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:
typedef union
{
struct {
unsigned char byte1;
unsigned char byte2;
unsigned char byte3;
unsigned char byte4;
} bytes;
unsigned int dword;
} HW_Register;
HW_Register reg;
Dann können Sie wie folgt auf die Registrierung zugreifen:
reg.dword = 0x12345678;
reg.bytes.byte3 = 4;
Endianness (Bytereihenfolge) und Prozessorarchitektur sind natürlich wichtig.
Ein weiteres nützliches Feature ist der Bitmodifikator:
typedef union
{
struct {
unsigned char b1:1;
unsigned char b2:1;
unsigned char b3:1;
unsigned char b4:1;
unsigned char reserved:4;
} bits;
unsigned char byte;
} HW_RegisterB;
HW_RegisterB reg;
Mit diesem Code können Sie direkt auf ein einzelnes Bit in der Register- / Speicheradresse zugreifen:
x = reg.bits.b2;
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.
typedef union {
unsigned char control_byte;
struct {
unsigned int nibble : 4;
unsigned int nmi : 1;
unsigned int enabled : 1;
unsigned int fired : 1;
unsigned int control : 1;
};
} ControlRegister;
Ich habe es in einigen Bibliotheken als Ersatz für objektorientierte Vererbung gesehen.
Z.B
Connection
/ | \
Network USB VirtualConnection
Wenn Sie möchten, dass die Verbindungsklasse eine der oben genannten Klassen ist, können Sie Folgendes schreiben:
struct Connection
{
int type;
union
{
struct Network network;
struct USB usb;
struct Virtual virtual;
}
};
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
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:
union {
int a;
int b;
int c;
} myUnion;
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.
Viele Verwendungen. Einfach machen grep union /usr/include/*
oder in ähnlichen Verzeichnissen. In den meisten Fällen union
wird das in ein struct
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:
struct _mydata {
int which_one;
union _data {
int a;
float b;
char c;
} foo;
} bar;
switch (bar.which_one)
{
case INTEGER : /* access bar.foo.a;*/ break;
case FLOATING : /* access bar.foo.b;*/ break;
case CHARACTER: /* access bar.foo.c;*/ break;
}
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:
set a to b times 7.
besteht aus folgenden Sprachelementen:
Sprachelemente wurden wie folgt definiert #define
:
#define ELEM_SYM_SET 0
#define ELEM_SYM_TO 1
#define ELEM_SYM_TIMES 2
#define ELEM_SYM_FULLSTOP 3
#define ELEM_VARIABLE 100
#define ELEM_CONSTANT 101
und die folgende Struktur wurde verwendet, um jedes Element zu speichern:
typedef struct {
int typ;
union {
char *str;
int val;
}
} tElem;
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:
tElem e;
e.typ = ELEM_SYM_SET;
Um ein "variable [b]" - Element zu erstellen, verwenden Sie:
tElem e;
e.typ = ELEM_VARIABLE;
e.str = strdup ("b"); // make sure you free this later
Um ein "Konstante [7]" - Element zu erstellen, verwenden Sie:
tElem e;
e.typ = ELEM_CONSTANT;
e.val = 7;
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
und val
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 basiert 0x1010
und Ganzzahlen und Zeiger beide sind 4 Bytes:
+-----------+
0x1010 | |
0x1011 | typ |
0x1012 | |
0x1013 | |
+-----+-----+
0x1014 | | |
0x1015 | str | val |
0x1016 | | |
0x1017 | | |
+-----+-----+
Wenn es nur in einer Struktur wäre, würde es so aussehen:
+-------+
0x1010 | |
0x1011 | typ |
0x1012 | |
0x1013 | |
+-------+
0x1014 | |
0x1015 | str |
0x1016 | |
0x1017 | |
+-------+
0x1018 | |
0x1019 | val |
0x101A | |
0x101B | |
+-------+
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:
struct variant {
int type;
double number;
char *string;
};
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:
struct variant {
int type;
union {
double number;
char *string;
} value;
};
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.
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:
typedef union MyUnion MYUNION;
union MyUnion
{
int MyInt;
float MyFloat;
};
MyUnion enthält nur ein int ODER ein float, je nachdem, welches Sie zuletzt festgelegt haben . Also mach das:
MYUNION u;
u.MyInt = 10;
u hält jetzt ein int gleich 10;
u.MyFloat = 1.0;
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.
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.
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.
typedef union
{
UINT8 buffer[PACKET_SIZE]; // Where the packet size is large enough for
// the entire set of fields (including the payload)
struct
{
UINT8 size;
UINT8 cmd;
UINT8 payload[PAYLOAD_SIZE];
UINT8 crc;
} fields;
}PACKET_T;
// This should be called every time a new byte of data is ready
// and point to the packet's buffer:
// packet_builder(packet.buffer, new_data);
void packet_builder(UINT8* buffer, UINT8 data)
{
static UINT8 received_bytes = 0;
// All range checking etc removed for brevity
buffer[received_bytes] = data;
received_bytes++;
// Using the struc only way adds lots of logic that relates "byte 0" to size
// "byte 1" to cmd, etc...
}
void packet_handler(PACKET_T* packet)
{
// Process the fields in a readable manner
if(packet->fields.size > TOO_BIG)
{
// handle error...
}
if(packet->fields.cmd == CMD_X)
{
// do stuff..
}
}
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.
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.
Gewerkschaftsereignis { unsigned long eventCode; unsigned char eventParts [4]; };
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.
In der Schule habe ich Gewerkschaften wie diese benutzt:
typedef union
{
unsigned char color[4];
int new_color;
} u_color;
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.
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:
union data {
int data;
struct {
unsigned char higher;
unsigned char lower;
} parts;
};
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:
union _Obj {
union _Obj* _M_free_list_link;
char _M_client_data[1]; /* The client sees this. */
};
struct
um dein higher
/ brauchen lower
? 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.
In früheren Versionen von C würden alle Strukturdeklarationen einen gemeinsamen Satz von Feldern verwenden. Gegeben:
struct x {int x_mode; int q; float x_f};
struct y {int y_mode; int q; int y_l};
struct z {int z_mode; char name[20];};
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
von struct 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 ein U*
entsprach es auf einen Zeiger auf jede Vereinigung Typ Gießen beide enthält T
und U
, und dann Gießen dass U*
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 von T
bis angegeben U
. In Fällen, in denen eine Funktion einen Zeiger unbekannter Herkunft empfangen hat, ist das Verhalten des Schreibens eines Objekts überT*
Konvertieren desT*
zu a U*
, und dann das Lesen des Objekts über U*
wäre gleichbedeutend mit dem Schreiben einer Vereinigung über ein Mitglied vom Typ T
und dem Lesen als Typ U
, 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.
foo
ein int
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 er anyPointer
identifiziert wird." jeder Strukturtyp, der foo
unter seinen Mitgliedern aufgeführt war.
anyPointer
mit einem Strukturelement identifiziert, wie überprüft der Compiler dann diesen Zustand to 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ürde foo
. Im Wesentlichen p->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:
union un
{
uint16_t array16[4];
uint32_t array32[2];
}
technisch ist dies immer noch ein Verstoß gegen die Regeln. Alle bekannten Standards unterstützen diese Verwendung.