C++联合体是共享内存的特殊类,所有成员共用同一块内存空间,大小由最大成员决定并按最大对齐要求对齐。

C++联合体,说白了,就是一种特殊的类,它让不同的数据成员共享同一块内存空间。这意味着,它的内存大小不是所有成员的总和,而是由它内部最大的那个成员所决定的,并且还会考虑内存对齐的要求。当你向联合体的一个成员写入数据时,实际上就覆盖了之前存储在那块内存中的其他成员的数据。
理解C++联合体(
union
struct
这种设计哲学带来了两个直接的后果:
char*
unsigned char*
std::bit_cast
关于大小计算,一个联合体的大小至少要能容纳其所有成员中最大的那个。但“至少”这个词很重要,因为内存对齐会介入。联合体的大小必须是其所有成员中最大对齐要求的倍数。例如,如果一个联合体包含
char
int
double
sizeof(double)
sizeof
立即学习“C++免费学习笔记(深入)”;
#include <iostream>
#include <string> // For std::string, though generally union with non-POD types is tricky
// 示例联合体
union Data {
int i;
float f;
char c;
double d; // 最大的成员
};
int main() {
Data myData;
std::cout << "Size of Data union: " << sizeof(myData) << " bytes" << std::endl;
std::cout << "Alignment of Data union: " << alignof(myData) << " bytes" << std::endl;
// 写入一个成员
myData.i = 123;
std::cout << "After writing myData.i = 123:" << std::endl;
std::cout << " myData.i: " << myData.i << std::endl;
// 此时访问其他成员是未定义行为,但为了演示内存共享,我们还是看一眼
// 注意:这里的输出结果是不可预测的,仅作演示
// std::cout << " myData.f (potentially garbage): " << myData.f << std::endl;
// std::cout << " myData.c (potentially garbage): " << myData.c << std::endl;
// 写入另一个成员,会覆盖之前的数据
myData.d = 3.14159;
std::cout << "After writing myData.d = 3.14159:" << std::endl;
std::cout << " myData.d: " << myData.d << std::endl;
// 此时myData.i的值已经被覆盖,再次访问是未定义行为
// std::cout << " myData.i (potentially garbage): " << myData.i << std::endl;
return 0;
}运行上述代码,你通常会看到
Size of Data union: 8 bytes
Alignment of Data union: 8 bytes
double
C++中的联合体(
union
struct
结构体是把多个不同类型的数据项打包成一个单一的复合类型。它的内存布局是:每个成员都占据自己独立的内存空间,并且按照它们在结构体中声明的顺序依次排列。当然,为了满足内存对齐的要求,编译器可能会在成员之间插入一些填充字节(padding)。因此,一个结构体的大小通常是其所有成员大小之和,再加上可能存在的填充字节。访问结构体成员时,它们的数据是完全独立的,互不影响。这很符合我们日常对“一组相关数据”的认知,比如一个学生的信息(姓名、学号、年龄),这些信息是并存的。
而联合体则不同,它的所有非静态数据成员都共享同一块内存空间,起始地址也相同。这意味着在任何给定时间点,联合体中只有一个成员可以“活跃”并持有有效数据。当你给联合体的一个成员赋值时,这块共享的内存就被这个成员的数据占据了,之前存储的任何其他成员的数据都会被覆盖。因此,联合体的大小仅仅是其最大成员的大小,并向上对齐到其所有成员中最大的对齐要求。它的设计初衷就是为了在多种可能的数据类型中,只存储其中一种,从而节省内存。
举个例子:
struct S {
char a;
int b;
double c;
};
union U {
char a;
int b;
double c;
};
// 在64位系统上,通常:
// sizeof(S) 可能是 16 或 24 字节 (取决于对齐和填充)
// a (1 byte) + padding (3 bytes) + b (4 bytes) + c (8 bytes) = 16 bytes
// sizeof(U) 肯定是 8 字节 (因为 double 是最大的,且对齐是8)所以,如果你需要同时存储多个数据项,并且它们之间逻辑上是独立的,那就用结构体。如果你只需要在多个数据项中选择一个来存储,并且注重内存效率,那么联合体可能是个选择,但要清楚它带来的类型安全挑战。
准确计算C++联合体的实际内存占用,不仅仅是找到最大成员的
sizeof
联合体的内存大小由以下两个主要因素决定:
我们来一步步分析:
sizeof()
alignof()
max_member_size
max_alignment_requirement
max_member_size
max_alignment_requirement
max_member_size
max_alignment_requirement
sizeof(union)
max_member_size
max_alignment_requirement
举个例子:
#include <iostream>
struct ExampleUnion {
char a; // sizeof=1, alignof=1
short b; // sizeof=2, alignof=2
int c; // sizeof=4, alignof=4
long long d; // sizeof=8, alignof=8 (通常)
};
union MyUnion {
char a;
short b;
int c;
long long d;
};
int main() {
std::cout << "sizeof(char): " << sizeof(char) << ", alignof(char): " << alignof(char) << std::endl;
std::cout << "sizeof(short): " << sizeof(short) << ", alignof(short): " << alignof(short) << std::endl;
std::cout << "sizeof(int): " << sizeof(int) << ", alignof(int): " << alignof(int) << std::endl;
std::cout << "sizeof(long long): " << sizeof(long long) << ", alignof(long long): " << alignof(long long) << std::endl;
std::cout << "\nsizeof(MyUnion): " << sizeof(MyUnion) << std::endl;
std::cout << "alignof(MyUnion): " << alignof(MyUnion) << std::endl;
return 0;
}在大多数64位系统上,
long long
MyUnion
max_member_size
sizeof(long long)
max_alignment_requirement
alignof(long long)
sizeof(MyUnion)
如果我们的联合体是这样:
union StrangeUnion {
char a[5]; // sizeof=5, alignof=1
int b; // sizeof=4, alignof=4
};max_member_size
sizeof(char[5])
max_alignment_requirement
alignof(int)
max_member_size
max_alignment_requirement
sizeof(StrangeUnion)
通过这种方式,我们能准确地预测联合体的内存占用,避免因对齐规则不熟悉而导致的误解。
联合体在C++中是一个相对低级且需要谨慎使用的特性,但它确实有一些特定的应用场景,同时也伴随着不小的潜在风险。
常见的应用场景:
内存优化(Memory Optimization): 这是联合体最直接的用途。当一个数据结构在不同时间点只需要存储多种类型中的一种时,使用联合体可以显著减少内存占用。例如,在一个图形渲染器中,一个“材质”对象可能包含纹理ID、颜色值或着色器参数,但同一时间只需要其中一种。
enum MaterialType { TEXTURE, COLOR, SHADER_PARAM };
struct Material {
MaterialType type;
union {
int textureId;
struct { float r, g, b, a; } color;
void* shaderHandle;
}; // 匿名联合体
};这里通过一个
type
实现变体类型(Variant Types): 在C++17引入
std::variant
VARIANT
硬件寄存器映射(Hardware Register Mapping): 在嵌入式系统编程中,有时会用联合体来定义硬件寄存寄存器的位域,以便于通过不同的方式访问同一块内存区域。
// 假设一个32位寄存器
union StatusRegister {
uint32_t raw; // 整个寄存器值
struct {
uint32_t errorFlag : 1; // 第0位是错误标志
uint32_t readyFlag : 1; // 第1位是就绪标志
uint32_t : 30; // 剩余位填充
} bits;
};这样,既可以整体读写寄存器
reg.raw
reg.bits.errorFlag
类型双关(Type Punning)/原始字节访问: 虽然大部分类型双关是未定义行为,但通过联合体和
char*
unsigned char*
潜在风险:
未定义行为(Undefined Behavior, UB): 这是使用联合体最主要的风险。标准规定,向联合体的一个成员写入数据后,除了通过
char*
unsigned char*
union Value {
int i;
float f;
};
Value v;
v.i = 10;
// std::cout << v.f; // 潜在的UB!类型安全缺失: 联合体本身不提供任何机制来追踪当前哪个成员是活跃的。你需要手动添加一个“标签”字段(如上面
Material
type
非平凡类型成员(Non-trivial Members)的限制: C++标准对联合体成员的类型有一些限制。如果联合体包含具有非平凡构造函数、析构函数、拷贝构造函数、移动构造函数、拷贝赋值运算符或移动赋值运算符的成员,那么使用联合体变得非常复杂。你需要手动管理这些成员的生命周期(例如,使用placement new和显式析构函数),否则会导致资源泄漏或未定义行为。通常,联合体成员最好是“平凡的”(Plain Old Data, POD)类型,或者在C++11后是“标准布局”(Standard Layout)和“平凡可复制”(Trivially Copyable)的类型。
代码可读性和维护性下降: 联合体的语义不如结构体直观,需要额外的判别逻辑。这使得代码更难阅读、理解和维护,尤其是在大型项目中。
鉴于这些风险,在现代C++中,除非有非常明确的内存或硬件交互需求,并且能够严格控制其使用,否则通常更推荐使用
std::variant
std::any
以上就是C++联合体内存共享与大小计算的详细内容,更多请关注php中文网其它相关文章!
每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。
Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号