为什么我们需要C工会?
什么时候应该使用工会?为什么我们需要它们?联合允许相互排斥的数据成员共享相同的内存。当内存更为稀缺时,这一点非常重要,例如在嵌入式系统中 在以下示例中:为什么我们需要C工会?,c,unions,C,Unions,什么时候应该使用工会?为什么我们需要它们?联合允许相互排斥的数据成员共享相同的内存。当内存更为稀缺时,这一点非常重要,例如在嵌入式系统中 在以下示例中: union { int a; int b; int c; } myUnion; 此并集将占用单个int的空间,而不是3个单独的int值。如果用户将值设置为a,然后将值设置为b,则会覆盖a的值,因为它们共享相同的内存位置。当您要对硬件、设备或网络协议定义的结构建模时,会使用联合,或者当您创建大量对象并希望节省空间时。不过,95
union {
int a;
int b;
int c;
} myUnion;
此并集将占用单个int的空间,而不是3个单独的int值。如果用户将值设置为a,然后将值设置为b,则会覆盖a的值,因为它们共享相同的内存位置。当您要对硬件、设备或网络协议定义的结构建模时,会使用联合,或者当您创建大量对象并希望节省空间时。不过,95%的时候你确实不需要它们,请坚持使用易于调试的代码。通常使用并集在整数和浮点的二进制表示形式之间进行转换:
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;
}
}
这使得结构的大小仅为12字节,而不是28字节。很难想象在特定情况下需要这种灵活的结构,也许在消息协议中,您将发送不同大小的消息,但即便如此,可能还有更好、更方便程序员的替代方案 联合有点像其他语言中的变体类型——它们一次只能保存一个对象,但该对象可以是int、float等,具体取决于您如何声明它 例如:
typedef union MyUnion MYUNION;
union MyUnion
{
int MyInt;
float MyFloat;
};
MyUnion将仅包含int或float,具体取决于您最近设置的值。这样做:
MYUNION u;
u.MyInt = 10;
u现在拥有一个等于10的整数
u.MyFloat = 1.0;
u现在持有等于1.0的浮点值。它不再保存int。显然,现在如果您尝试执行printf(“MyInt=%d”,u.MyInt);然后你可能会得到一个错误,尽管我不确定具体的行为
联合体的大小由其最大字段的大小决定,在本例中为浮点。联合体非常好。我见过的联合的一个巧妙用法是在定义事件时使用它们。例如,您可以确定事件是32位的 现在,在这32位中,您可能希望将前8位指定为事件发送方的标识符。。。有时您将事件作为一个整体来处理,有时您将其分解并比较其组件。工会让你可以灵活地同时做这两件事 union Event { unsigned long eventCode; unsigned char eventParts[4]; }; 工会活动 { 无符号长事件码; 无符号字符事件部分[4]; };
下面是一个来自我自己的代码库的联合示例(来自内存并进行了解释,因此可能不准确)。它被用来在我构建的解释器中存储语言元素。例如,以下代码:
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;
tElem e;
e.typ = ELEM_VARIABLE;
e.str = strdup ("b"); // make sure you free this later
tElem e;
e.typ = ELEM_CONSTANT;
e.val = 7;
为了创建“变量[b]”元素,您将使用:
tElem e;
e.typ = ELEM_SYM_SET;
tElem e;
e.typ = ELEM_VARIABLE;
e.str = strdup ("b"); // make sure you free this later
tElem e;
e.typ = ELEM_CONSTANT;
e.val = 7;
为了创建“常量[7]”元素,您可以使用:
tElem e;
e.typ = ELEM_SYM_SET;
tElem e;
e.typ = ELEM_VARIABLE;
e.str = strdup ("b"); // make sure you free this later
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
{
unsigned char color[4];
int new_color;
} u_color;
联合在嵌入式编程或需要直接访问硬件/内存的情况下特别有用。以下是一个简单的例子:
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.dword = 0x12345678;
reg.bytes.byte3 = 4;
Endianness(字节顺序)和处理器架构当然很重要
另一个有用的功能是位修饰符:
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;
在COM接口中使用的是什么?它有两个字段——“type”和一个union,其中包含一个实际值,该值根据“type”字段进行处理。我在为嵌入式设备编码时使用union。我有16位长的C int。当我需要读取/存储EEPROM时,我需要检索较高的8位和较低的8位。所以我用这种方式:
union data {
int data;
struct {
unsigned char higher;
unsigned char lower;
} parts;
};
它不需要移位,因此代码更容易阅读
另一方面,我看到了一些用C++作为STL分配器的C++ STL代码。如果您感兴趣,可以阅读源代码。以下是其中的一部分:
union _Obj {
union _Obj* _M_free_list_link;
char _M_client_data[1]; /* The client sees this. */
};
- 包含不同记录类型的文件
- 包含不同请求类型的网络接口
许多可能的X.25命令中的一个被接收到缓冲区中,并通过使用所有可能的结构的并集进行就地处理。我想说,这样可以更容易地重用可能以不同方式使用的内存,即节省内存。例如,您希望使用能够保存短字符串和数字的“variant”结构:
struct variant {
int type;
double number;
char *string;
};
在32位系统中,这将导致至少96位或1位
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..
}
}
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];};