什么时候应该使用工会?我们为什么需要它们?
当前回答
联合在嵌入式编程或需要直接访问硬件/内存的情况下特别有用。这里有一个简单的例子:
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;
其他回答
有很多用法。只需执行grep union /usr/include/*或类似目录。大多数情况下,联合被包装在结构中,结构的一个成员告诉联合中的哪个元素可以访问。例如,为现实生活的实现签出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;
然后,每个元素的大小是最大联合的大小(typ为4字节,联合为4字节,尽管这些是典型值,但实际大小取决于实现)。
为了创建一个“set”元素,你可以使用:
tElem e;
e.typ = ELEM_SYM_SET;
为了创建一个“variable[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 | |
+-------+
联合在嵌入式编程或需要直接访问硬件/内存的情况下特别有用。这里有一个简单的例子:
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;
我想说,它可以更容易地重用可能以不同方式使用的内存,即节省内存。例如,你想做一些“变体”结构体,能够保存一个短字符串以及一个数字:
struct variant {
int type;
double number;
char *string;
};
在32位系统中,这将导致每个变体实例至少使用96位或12个字节。
使用联合可以将大小减小到64位或8字节:
struct variant {
int type;
union {
double number;
char *string;
} value;
};
如果你想添加更多不同的变量类型,你甚至可以保存更多。这可能是真的,你可以做类似的事情,强制转换一个空指针-但联合使它更容易访问,以及类型安全。这样的节省听起来并不是很大,但是您节省了用于该结构的所有实例的三分之一的内存。
许多答案都涉及从一种类型转换到另一种类型。我从具有相同类型的联合中得到最多的使用(即在解析串行数据流时)。它们允许解析/构造一个有框架的包变得很简单。
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..
}
}
编辑 关于字节序和结构填充的评论是有效的,而且非常值得关注。我几乎完全在嵌入式软件中使用了这段代码,其中大部分我都可以控制管道的两端。