struct
和union
之间的区别?
基本上我知道struct
使用其成员的所有内存,而union
使用最大成员的内存空间。在操作系统层面上还有其它的区别吗?使用 union 时,你只需要使用其中的一个元素,因为它们都存储在同一位置。这使得当你想要存储多种类型中的一种时非常有用。另一方面,结构体对于其每个元素都有单独的内存位置,因此它们都可以同时使用。
为了给出一个具体的使用示例,我之前正在开发一个 Scheme 解释器,实际上是将 Scheme 数据类型覆盖到 C 数据类型上。这涉及到在结构体中存储表示值类型的枚举和一个 union 来存储该值。
union foo {
int a; // can't use both a and b at once
char b;
} foo;
struct bar {
int a; // can use both a and b simultaneously
char b;
} bar;
union foo x;
x.a = 3; // OK
x.b = 'c'; // NO! this affects the value of x.a!
struct bar y;
y.a = 3; // OK
y.b = 'c'; // OK
编辑: 如果你想知道将x.b设置为'c'会改变x.a的值,严格来说是未定义的。在大多数现代计算机上,char是1个字节,int是4个字节,因此给x.b赋值'c'也会使x.a的第一个字节具有相同的值:union foo x;
x.a = 3;
x.b = 'c';
printf("%i, %i\n", x.a, x.b);
99, 99
union foo x;
x.a = 387439;
x.b = 'c';
printf("%i, %i\n", x.a, x.b);
打印
387427, 99
union foo x;
x.a = 0xDEADBEEF;
x.b = 0x22;
printf("%x, %x\n", x.a, x.b);
打印
deadbe22, 22
你可以清楚地看到0x22覆盖了0xEF的位置。
但是
在C语言中,一个int类型中字节的顺序未定义。这个程序在我的Mac电脑上用0x22覆盖了0xEF,但是在其他平台上,它可能用相同的方法覆盖0xDE,因为组成int类型的字节的顺序会被反转。因此,在编写程序时,永远不要依赖于联合中覆盖特定数据的行为,因为它是不可移植的。
有关字节顺序的更多阅读,请查看字节顺序。
这里是简短的答案: 结构体是一种记录结构:结构体中的每个元素都会分配新的空间。因此,像下面这样的结构体
struct foobarbazquux_t {
int foo;
long bar;
double baz;
long double quux;
}
每个实例在内存中至少分配了(sizeof(int)+sizeof(long)+sizeof(double)+sizeof(long double))
字节。这是因为体系结构对齐限制可能会强制编译器填充结构体。(“至少”是因为体系结构对齐限制可能会强制编译器填充结构体。)
另一方面,
union foobarbazquux_u {
int foo;
long bar;
double baz;
long double quux;
}
分配一个内存块并给它四个别名。因此,sizeof(union foobarbazquux_u) ≥ max((sizeof(int),sizeof(long),sizeof(double),sizeof(long double))
,再加上对齐的可能性。
有没有一个好的例子可以说明'struct'和'union'之间的区别?
一个想象中的通信协议
struct packetheader {
int sourceaddress;
int destaddress;
int messagetype;
union request {
char fourcc[4];
int requestnumber;
};
};
在这个虚构的协议中,规定了基于“消息类型”,标头中的以下位置将是请求编号或四个字符代码之一,但不会同时出现。 简而言之,联合允许同一存储位置表示多个数据类型,其中保证您每次只想存储一种数据类型。
联合通常是 C 语言作为系统编程语言的遗产中的低级细节,有时会以这种方式使用“重叠”的存储位置。 当您有一个数据结构,在其中仅保存其中几种类型之一时,有时可以使用联合来节省内存。
总的来说,操作系统并不关心结构体和联合体 - 对它们而言,它们都只是一块内存块。 结构体是存储多个数据对象的内存块,其中这些对象不重叠。 联合体是存储多个数据对象的内存块,但仅具有这些对象中最大的存储空间,因此一次只能存储一个数据对象。
packetheader ph;
,如何访问 requestnumber?是 ph.request.requestnumber
吗? - justin.m.chase正如您在问题中所提到的,union
和struct
的主要区别在于union
成员重叠彼此的内存空间,因此union
的大小是其中最大成员的大小,而struct
成员则一个接一个地排列(之间可以选择性地添加填充)。此外,一个 union 大到足以容纳其所有成员,并具有适合其成员的对齐方式。假设说,int
只能存储在 2 字节地址上,宽度为 2 字节,long
只能存储在 4 字节地址上,长度为 4 字节。以下是一个 union:
union test {
int a;
long b;
};
一个sizeof
大小可以为4,并且对齐需求为4。 union和struct在结尾处都可以有填充,但在开头不能有。写入struct仅更改已写入成员的值。写入union的成员将使所有其他成员的值无效。如果之前没有写入它们,则无法访问它们,否则行为未定义。 GCC作为扩展提供了一种方法,即使您最近没有写入它们,您也可以实际上从union的成员中读取。对于操作系统来说,用户程序是向联合体还是结构体写入并不重要。这实际上只是编译器的问题。
另一个重要的union和struct的属性是它们允许指向它们的指针指向其任何成员类型。因此,以下是有效的:
struct test {
int a;
double b;
} * some_test_pointer;
some_test_pointer可以指向int*
或double*
。如果您将类型为test
的地址强制转换为int*
,它将实际上指向其第一个成员a
。同样,联合也是如此。因此,由于联合始终具有正确的对齐方式,您可以使用联合使指向某些类型变得有效:
union a {
int a;
double b;
};
该联合体实际上能够指向一个int和一个double:
union a * v = (union a*)some_int_pointer;
*some_int_pointer = 5;
v->a = 10;
return *some_int_pointer;
根据C99标准,实际上是有效的
:
对象的存储值只能被具有以下类型之一的lvalue表达式访问:
- 与对象的有效类型兼容的类型
- ...
- 包含上述类型之一的聚合或联合类型中的一个成员
编译器不会优化掉v->a = 10;
,因为它可能会影响*some_int_pointer
的值(函数将返回10
而不是5
)。
union
在一些场景中非常有用。比如,union
可以用于非常低级别的操作,例如为内核编写设备驱动程序。
一个例子是通过使用struct
和float
的位域的union
来分解float
数值。我将一个数保存在float
中,稍后可以通过那个struct
访问float
的特定部分。该示例展示了如何使用union
从不同角度查看数据。
#include <stdio.h>
union foo {
struct float_guts {
unsigned int fraction : 23;
unsigned int exponent : 8;
unsigned int sign : 1;
} fg;
float f;
};
void print_float(float f) {
union foo ff;
ff.f = f;
printf("%f: %d 0x%X 0x%X\n", f, ff.fg.sign, ff.fg.exponent, ff.fg.fraction);
}
int main(){
print_float(0.15625);
return 0;
}
请查看维基百科上有关单精度的描述,我使用了那里的例子和魔数0.15625。
union
也可用于实现具有多个备选项的代数数据类型。我在 O'Sullivan、Stewart 和 Goerzen 的《Real World Haskell》书中找到了一个例子。请在“区分联合”部分查看。
干杯!
是的,结构体和联合体之间的主要区别与您所述的相同。
结构体使用其成员的所有内存,而联合使用最大成员的内存空间。
但所有的区别都在于内存的使用需求。联合的最佳用法可以在 Unix 进程中看到,我们在其中利用信号,例如一个进程一次只能对一个信号进行操作。因此,一般声明将为:
union SIGSELECT
{
SIGNAL_1 signal1;
SIGNAL_2 signal2;
.....
};
在这种情况下,过程仅使用所有信号中最高的内存。但是,如果您在此情况下使用结构,则内存使用量将是所有信号的总和。 这会产生很大的差异。非技术性地讲意味着:
假设:椅子=内存块,人=变量
结构体:如果有3个人,他们可以相应地坐在大小合适的椅子上。
联合体:如果有3个人,只有一个椅子可供使用,当他们想要坐下时,所有人都需要使用相同的椅子。
技术性地讲意味着:
下面的程序深入探讨了结构体和联合体。
struct MAIN_STRUCT
{
UINT64 bufferaddr;
union {
UINT32 data;
struct INNER_STRUCT{
UINT16 length;
UINT8 cso;
UINT8 cmd;
} flags;
} data1;
};
对于缓冲区地址和联合体的sizeof(UNIT32),再加上取决于处理器架构的填充位32位,主要结构的总大小为128位。
对于结构体,所有成员都会连续地得到内存块。
联合体获取一个最大成员(这里是32位)的内存块。在联合体内部还有一个结构体(INNER_STRUCT),其成员获得总大小为32位(16+8+8)的内存块。在联合体中,可以访问INNER_STRUCT(32位)成员或数据(32位)。
你已经拥有它了,这就是全部。
那么,联合的要点是什么?
你可以在同一个位置存储不同类型的内容。你必须知道你存储在联合中的类型(所以通常把它放在具有类型标记的struct
中...)。
为什么这很重要?这并不是为了节省空间。是的,你可以节省一些位或进行一些填充,但这不再是主要因素。
这是为了类型安全,它使你能够进行某种形式的“动态类型检查”:编译器知道你的内容可能有不同的含义,而你在运行时精确解释它的含义取决于你自己。如果你有一个指针可以指向不同类型,则必须使用联合,否则由于别名问题,你的代码可能是不正确的(编译器会对自己说“哦,只有这个指针可以指向这个类型,所以我可以优化掉那些访问...”,会发生糟糕的事情)。
结构体分配的内存大小是其中所包含元素大小总和。
联合体只会分配最大成员所需的内存空间。