在C语言中,“union”和“struct”的主要区别是什么?

27

可能重复:
C语言中结构体和联合体的区别

我能理解什么是结构体。但是,我对联合体和结构体之间的区别有点困惑。联合体就像是一块内存共享。这到底是什么意思?


1
请注意,如果您编写了一个联合体的成员,则读取任何其他成员将是未定义行为。 - GManNickG
2
已有很多重复的内容,如C语言中结构体和联合体的区别 - Paul R
GMan:据我所知,正确的说法应该是“...任何不是unsigned char数组的其他成员”。 - Jens Gustedt
7个回答

33

使用 union,所有成员共享相同的内存。使用 struct,则不共享内存,因此为结构的每个成员分配了不同的内存空间。

例如:

union foo
{
 int x;
 int y;
};

foo f;
f.x = 10;
printf("%d\n", f.y);

在这里,我们将值10赋给foo::x。然后输出foo::y的值,它也是10,因为x和y共享同一块内存。请注意,由于联合体的所有成员都共享同一块内存,编译器必须分配足够的内存来适应联合体的最大成员。因此,包含一个char和一个long的联合体需要足够的空间来容纳long。但如果我们使用结构体:
struct foo
{
 int x;
 int y;
};

foo f;
f.x = 10;
f.y = 20;
printf("%d %d\n", f.x, f.y);

我们将10分配给x,20分配给y,然后将它们都打印出来。我们看到x是10,y是20,因为x和y没有共享相同的内存。
编辑:还要注意Gman上面的评论。我提供的联合示例仅用于演示目的。在实践中,您不应该写入联合的一个数据成员,然后访问另一个数据成员。通常,这只会导致编译器将位模式解释为另一种类型,但由于这样做是未定义行为,因此可能会得到意外的结果。

谢谢你的回答,Charles。但是,我想知道它们将共享哪些内存或为联合分配了多少内存..? - Vinay
谢谢你,Charles。现在我明白了。因此,通过在联合中使用两种不同的数据类型,我们分配内存以适应最大的成员,并与所有其他成员共享它。 - Vinay
3
值得指出的是,工会存在的原因是为了代表类型为A或B的某个实体,而不是同时代表两者。这与ML、Haskell等语言中松散对应的和类型有相似之处。 - wnoise

12

我使用联合体将字节转换为其他类型,我发现这比位移更容易。

union intConverter {
    int intValue;
    struct {
        byte hi;
        byte lo;
    } byteValue;
}

intConverter cv;
cv.intValue =1100;
printf("%X %X\n", cv.byteValue.hi, cv.byteValue.lo);

int指的是16位(曾在微控制器上使用)。


4
这是一种危险的使用方式,因为无法保证你的“hi”字节实际上是高位字节。 - JUST MY correct OPINION
3
是的,您确实需要知道它正在运行的字节序。 - Sam

5

一个union的每个成员共享同一块内存。这意味着如果你改变其中一个成员,其他成员也会随之改变。而且,如果成员是不同类型的,可能会产生不可预测的结果(虽然不完全是不可预测的,但除非你了解组成数据成员的底层位模式,否则很难预测)。


谢谢你,"PigBen"。很高兴知道当数据类型不同时,成员的更改会导致一些垃圾结果。 :) - Vinay

3

我认为可以这样理解union:它是一组不同类型的别名,指向同一个内存块。每个成员都是一个“别名”,具有特定的类型。每个别名都引用内存中相同的地址。如何解释该地址上的位由别名的类型确定。

union占用的内存量始终等于或可能大于union中最大大小的“成员”(由于对齐限制)。


谢谢“Jeff M”。您回答说,由于对齐限制,所占用的内存相等或可能更大。什么是“对齐限制”? - Vinay
1
@Vinay:请参考这个答案。简而言之,类型需要存储在特定的内存地址上。例如,32位整数存储在4的倍数地址上,16位短整数存储在2的倍数地址上等等。因此,结构体将添加填充以适应此要求。联合也是如此。 - Jeff Mercado
谢谢您。答案很有帮助且信息丰富。 - Vinay

3
有一个未经雕琢的例子会更有用,以便说明这对什么有用。(我说“未经雕琢”,因为大多数使用union进行位操作的用途都非常棘手。将从大端序硬件转换为小端序硬件的位操作联合体在最初时会以最难以理解的方式出现故障。)(当然,我已经编写了位操作联合体来分解浮点数以实现比库函数快几个数量级的数学函数。我只是添加有关应该具有相同地址的成员的断言。)
结构体option1 { int type; /* 其他成员 */ }; 结构体option2 { int type; /* 其他成员 */ }; 结构体option3 { int type; /* 其他成员 */ }; 联合体combo { int type; //保证与结构体int型的type完全重叠。 结构体option1; 结构体option2; 结构体option3; }; //... void foo(union combo *in) { switch(in.type) { case 1: { 结构体option1 *bar = in; //然后处理一个option1类型的请求 } case 2: { 结构体option2 *bar = in; //然后处理一个option2类型的请求 } case 3: { 结构体option3 *bar = in; //然后处理一个option3类型的请求 } } }
这种构造在X编程和其他需要使函数能够接收许多不同类型的消息(具有不同参数和布局要求)的情况下非常常见。

在 C 语言(甚至是 C99)中,在标签后面进行声明,如 case 1: 是非法的。此外,标识符 option1 等都是标签,因此您需要用 struct option1 *bar 进行声明。如果想要声明变量,可以在每个 case 标签后引入新的作用域。 - dreamlax
@dreamlax:不简化的代码会降低可读性。 - Eric Towers

2

这里的大多数答案都是正确的。联合体本质上是一种以不同方式访问相同数据的方法(例如,您可以将内存中的4个字节作为1个整数或4个字符来访问/解释)。正如您所知,结构体是直接的 - 一个包含不同、独立对象及其自己的内存的集合。

通常情况下,与结构体相比,您在编程的后期才需要使用联合体。


1

运行这个程序并找到输出。

#include < stdio.h >

int main()
{
  union _testUnion
  {
    long long x;
    long long y;
  } testUnion;
struct _testStruct { long long x; long long y; }testStruct; printf("Union的大小为%d\n",sizeof(testUnion)); printf("Struct的大小为%d\n",sizeof(testStruct)); return; }

你会发现结构体的大小是联合体的两倍。这是因为联合体只为一个变量分配了空间,而结构体为两个变量分配了空间。


谢谢你,Manoj。是的,我理解了你回复的内容。很高兴知道这一点。 - Vinay

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接