什么时候应该使用工会?我们为什么需要它们?
联合通常用于在整数和浮点数的二进制表示之间进行转换:
union { int i; float f; } u; // Convert floating-point bits to integer: u.f = 3.14159f; printf("As integer: %08x\n", u.i);
虽然根据C标准,这是技术上未定义的行为(您只应阅读最近编写的字段),但它几乎可以在任何编译器中以明确定义的方式运行.
联合还有时用于在C中实现伪多态,通过给结构一些标记来指示它包含的对象类型,然后将可能的类型组合在一起:
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; } }
这允许大小struct S
只有12个字节,而不是28个字节.
联合在嵌入式编程或需要直接访问硬件/内存的情况下特别有用.这是一个简单的例子:
typedef union { struct { unsigned char byte1; unsigned char byte2; unsigned char byte3; unsigned char byte4; } bytes; unsigned int dword; } HW_Register; HW_Register reg;
然后您可以按如下方式访问reg:
reg.dword = 0x12345678; reg.bytes.byte3 = 4;
字节顺序(字节顺序)和处理器架构当然很重要.
另一个有用的功能是位修饰符:
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;
使用此代码,您可以直接访问寄存器/存储器地址中的单个位:
x = reg.bits.b2;
低级系统编程是一个合理的例子.
IIRC,我使用了工会将硬件寄存器分解为组件位.因此,您可以访问一个8位寄存器(就像我在这一天那样;-)进入组件位.
(我忘记了确切的语法但是......)这种结构允许控制寄存器作为control_byte或通过各个位进行访问.确保位映射到给定字节序的正确寄存器位是很重要的.
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;
我在几个库中看到它作为面向对象继承的替代品.
例如
Connection / | \ Network USB VirtualConnection
如果你想让Connection"class"成为上述任何一个,你可以这样写:
struct Connection { int type; union { struct Network network; struct USB usb; struct Virtual virtual; } };
在libinfinity中使用的示例:http://git.0x539.de/?p = ininote.git; a = blob; f = libinfinity/common/infas-call.c; h = 3e887f0d63bd754c6b5ec232948027cbbf4d61fc; hb = HEAD#l74
联合允许互斥的数据成员共享相同的内存.当内存更加稀缺时,这一点非常重要,例如在嵌入式系统中.
在以下示例中:
union { int a; int b; int c; } myUnion;
此并集将占用单个int的空间,而不是3个单独的int值.如果用户设置的值一个,然后设置的值b,它会覆盖的值一,因为它们都共享相同的存储位置.
很多用法.只是做grep union /usr/include/*
或在类似的目录.大多数情况union
都包含在一个struct
结构的一个成员中,它告诉联合中哪个元素可以访问.例如,结帐man elf
现实生活.
这是基本原则:
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; }
这是一个来自我自己的代码库的联合的例子(来自内存和转述所以它可能不准确).它用于在我构建的解释器中存储语言元素.例如,以下代码:
set a to b times 7.
由以下语言元素组成:
符号[组]
变量并[a]
符号[到]
变量并[b]
符号[倍]
常数[7]
符号[.]
语言元素被定义为' #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
以下结构用于存储每个元素:
typedef struct { int typ; union { char *str; int val; } } tElem;
那么每个元素的大小就是最大联合的大小(类型为4个字节,联合为4个字节,尽管这些是典型值,实际大小依赖于实现).
要创建"set"元素,您可以使用:
tElem e; e.typ = ELEM_SYM_SET;
要创建"变量[b]"元素,您可以使用:
tElem e; e.typ = ELEM_VARIABLE; e.str = strdup ("b"); // make sure you free this later
要创建"常量[7]"元素,您可以使用:
tElem e; e.typ = ELEM_CONSTANT; e.val = 7;
你可以轻松扩展它以包括浮点数(float flt
)或有理数(struct ratnl {int num; int denom;}
)和其他类型.
基本前提是内存中str
并且val
不连续,它们实际上是重叠的,因此它是一种在同一内存块上获得不同视图的方法,如图所示,其中结构基于内存位置0x1010
,整数和指针都是4字节:
+-----------+ 0x1010 | | 0x1011 | typ | 0x1012 | | 0x1013 | | +-----+-----+ 0x1014 | | | 0x1015 | str | val | 0x1016 | | | 0x1017 | | | +-----+-----+
如果它只是在一个结构中,它看起来像这样:
+-------+ 0x1010 | | 0x1011 | typ | 0x1012 | | 0x1013 | | +-------+ 0x1014 | | 0x1015 | str | 0x1016 | | 0x1017 | | +-------+ 0x1018 | | 0x1019 | val | 0x101A | | 0x101B | | +-------+
我想说它可以更容易地重用可能以不同方式使用的内存,即节省内存.例如,你想做一些能够保存短字符串和数字的"变体"结构:
struct variant { int type; double number; char *string; };
在32位系统中,这将导致每个实例使用至少96位或12个字节variant
.
使用联合,您可以将大小减小到64位或8字节:
struct variant { int type; union { double number; char *string; } value; };
如果你想添加更多不同的变量类型等,你可以节省更多.可能是真的,你可以做类似的事情来构建一个void指针 - 但是union使得它更容易访问以及类型安全.这样的节省听起来不是很大,但是你节省了三分之一用于这个结构的所有实例的内存.
当你需要这种类型的灵活结构时,很难想到一个特定的场合,也许是在你要发送不同大小的消息的消息协议中,但即使这样,也可能有更好的程序员友好的替代方案.
联盟有点像其他语言中的变体类型 - 它们一次只能容纳一个东西,但是这个东西可能是int,float等等,这取决于你如何声明它.
例如:
typedef union MyUnion MYUNION; union MyUnion { int MyInt; float MyFloat; };
MyUnion将只包含一个int OR浮点数,具体取决于您最近设置的值.这样做:
MYUNION u; u.MyInt = 10;
你现在拥有一个等于10的int;
u.MyFloat = 1.0;
你现在拥有一个等于1.0的浮点数.它不再持有int.显然现在如果你尝试做printf("MyInt =%d",u.MyInt); 然后你可能会得到一个错误,虽然我不确定具体的行为.
联合的大小取决于其最大字段的大小,在本例中为浮点数.