Answers:
联合通常用于整数和浮点数的二进制表示形式之间的转换:
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.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
如果您希望连接“类”为上述任一类,则可以编写如下内容:
struct Connection
{
int type;
union
{
struct Network network;
struct USB usb;
struct Virtual virtual;
}
};
在libinfinity中使用的示例:http ://git.0x539.de/?p=infinote.git;a=blob;f=libinfinity/common/inf-session.c;h=3e887f0d63bd754c6b5ec232948027cbbf4d61fc;hb=HEAD#l74
很多用法。只是做grep union /usr/include/*
或在类似的目录中。在大多数情况下,union
都用a包裹,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.
由以下语言元素组成:
语言元素被定义为' #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;
为了创建“ variable [b]”元素,您可以使用:
tElem e;
e.typ = ELEM_VARIABLE;
e.str = strdup ("b"); // make sure you free this later
为了创建“ constant [7]”元素,可以使用:
tElem e;
e.typ = ELEM_CONSTANT;
e.val = 7;
并且您可以轻松地将其扩展为包括float(float flt
)或有理数(struct ratnl {int num; int denom;}
)和其他类型。
基本前提是,str
and 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 | |
+-------+
make sure you free this later
应从常量元素中删除注释?
我要说的是,它可以更轻松地重用可能以不同方式使用的内存,即节省内存。例如,您想执行一些“变体”结构,该结构可以保存短字符串和数字:
struct variant {
int type;
double number;
char *string;
};
在32位系统中,这将导致至少有96位或12个字节用于以下情况的每个实例: variant
。
使用联合可以将大小减小到64位或8个字节:
struct variant {
int type;
union {
double number;
char *string;
} value;
};
如果您想添加更多不同的变量类型等,则可以节省更多。的确,您可以执行类似的操作来铸造空指针-但是联合使它更易于访问,而且类型安全。这样的节省听起来并不庞大,但是您节省了用于该结构所有实例的三分之一的内存。
在特定情况下,当您需要这种灵活的结构时,可能很难想到,也许在消息协议中,您将发送不同大小的消息,但是即使那样,也可能会有更好,对程序员更友好的替代方法。
联合有点像其他语言中的变体类型-一次只能容纳一个东西,但是该东西可以是int,float等,具体取决于声明方式。
例如:
typedef union MyUnion MYUNION;
union MyUnion
{
int MyInt;
float MyFloat;
};
MyUnion将仅包含int或float,这取决于您最近设置的。这样做:
MYUNION u;
u.MyInt = 10;
您现在拥有一个等于10的整数;
u.MyFloat = 1.0;
您现在拥有等于1.0的浮点数。它不再拥有一个整数。显然,现在如果您尝试执行printf(“ MyInt =%d”,u.MyInt); 那么您可能会得到一个错误,尽管我不确定特定的行为。
联合的大小由其最大字段的大小(在本例中为float)决定。
sizeof(int) == sizeof(float)
(== 32
)通常。
这些答案中有许多涉及从一种类型转换为另一种类型。我从同类型的联合中获得最多的利用,而更多是同类型的(即,在解析串行数据流时)。它们允许解析/构造成帧的数据包变得微不足道。
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..
}
}
编辑 有关字节序和结构填充的注释是有效的,也是非常重要的问题。我几乎完全在嵌入式软件中使用过这部分代码,其中大部分我可以控制管道的两端。
我在为嵌入式设备编码时使用了union。我有16位长的C int。当我需要从EEPROM读取/存储时,我需要检索高8位和低8位。所以我用这种方式:
union data {
int data;
struct {
unsigned char higher;
unsigned char lower;
} parts;
};
它不需要移位,因此代码更易于阅读。
另一方面,我看到了一些旧的C ++ stl代码,这些代码使用union作为stl分配器。如果您有兴趣,可以阅读sgi stl源代码。这是其中的一部分:
union _Obj {
union _Obj* _M_free_list_link;
char _M_client_data[1]; /* The client sees this. */
};
struct
在higher
/ 周围进行分组lower
?现在,两者都应仅指向第一个字节。
在早期的C版本中,所有结构声明都将共享一组公共字段。鉴于:
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];};
编译器本质上将产生一个结构大小(以及可能的对齐方式)表,以及一个单独的结构成员名称,类型和偏移量表。编译器不跟踪哪些成员属于其结构,并允许两个结构具有相同名称的成员只有在类型和偏移匹配(如成员q
的struct x
和struct y
)。如果p是指向任何结构类型的指针,则p-> q会将“ q”的偏移量添加到指针p并从结果地址中获取“ int”。
给定以上语义,可以编写一个可以在多种结构上交替执行一些有用操作的函数,前提是该函数使用的所有字段都与所讨论结构中的有用字段对齐。这是一个有用的功能,将C更改为针对所讨论的结构类型来验证用于结构访问的成员将意味着在没有一种结构可以在同一地址包含多个命名字段的方法的情况下丢失它。在C语言中添加“联盟”类型有助于某种程度地弥补这一差距(尽管恕我直言,但它本来应该如此)。
工会填补这一空白的能力的重要组成部分是,可以将指向工会成员的指针转换为指向包含该成员的任何工会的指针,并且可以将指向任何工会的指针转换为针对任何成员的指针。尽管C89标准没有明确表示T*
直接将a 强制转换为a U*
等同于将其强制转换为指向同时包含T
和的任何联合类型的指针U
,然后将其强制转换为U*
,但后一种强制转换序列的定义行为均不会受到使用的联合类型,并且标准没有为从T
到直接的转换指定任何相反的语义U
。此外,在函数接收到来源未知的指针的情况下,通过对象写入的行为T*
将转换为T*
到a U*
,然后通过读取对象U*
等同于通过type的成员编写并写T
为type的联合U
,这在某些情况下(例如,在访问Common Initial Sequence成员时)是标准定义的,而实现是定义的(而是比未定义)。尽管程序很少使用带有联合类型的实际对象的CIS保证,但是利用这样的事实是更为普遍的:指向未知来源的对象的指针的行为必须类似于指向联合成员的指针,并且具有与之关联的行为保证。
foo
是int
偏移量为8的成员,则anyPointer->foo = 1234;
意味着“在anyPointer中获取地址,将其移位8个字节,然后对所得地址执行值1234的整数存储。编译器无需知道或关心是否已anyPointer
标识foo
anyPointer
标识符是否带有struct成员,那么编译器将如何检查to have a member with the same name only if the type and offset matched
您的帖子的这些条件?
p->foo
取决于的类型和偏移量foo
。本质上p->foo
是的简写*(typeOfFoo*)((unsigned char*)p + offsetOfFoo)
。关于后一个问题,当编译器遇到结构成员定义时,它要求不存在具有该名称的成员,或者具有相同名称和类型的成员;我猜想如果存在不匹配的struct成员定义,那会很困扰,但是我不知道它是如何处理错误的。