> 联合体(共用内存)与枚举(可读常量):内存布局、互斥标记、可移植对齐、枚举与switch
最佳实践,精准省内存!
一、联合体 vs 结构体:内存视角
特性 | 结构体 struct |
联合体 union |
---|---|---|
内存分配 | 各成员独立地址 | 所有成员共用同一地址 |
总大小 | 各成员大小和(+对齐填充) | 最大成员大小(+对齐填充) |
同时有效性 | ✅ 全部成员可同时有效 | ❌ 某一时刻仅一个成员有效 |
典型用途 | 聚合数据 | 互斥数据、节省内存、协议字段 |
> 口诀:结构体是「套房」,联合体是「共享办公桌」
基本概念
联合体的外在形式跟结构体非常类似,但它们有一个本质的区别:结构体中的各个成员是各自独立的,而联合体中的各个成员却共用同一块内存,因此联合体也称为共用体。
联合体各成员的堆叠效果

联合体内部成员的这种特殊的“堆叠”效果,使得联合体有如下基本特征:
- 整个联合体变量的尺寸,取决于联合体中尺寸最大的成员。
- 给联合体的某个成员赋值,会覆盖其他的成员,使它们失效。
- 联合体各成员之间形成一种“互斥”的逻辑(成员之间会互相覆盖),在某个时刻只有一个成员有效。
- 因此联合体一般不会单独使用,而是需要配合一个标记来使用。
二、联合体基础操作
① 声明与定义
声明语法:
- 联合体标签,用来区分各个不同的联合体。
- 成员,是包含在联合体内部的数据,可以是任意的数据类型。
union 联合体标签
{成员1;成员2;...
};// 定义了一种称为 union attr 的联合体类型
union attr
{int x;char y;double z;
};int main()
{// 定义联合体变量union attr n;
}
② 初始化(仅第一个成员生效)
联合体的操作跟结构体形式上别无二致,但由于联合体特殊的存储特性,不管怎么初始化和赋值,最终都有且仅有一个成员是有效的。
// 普通初始化:第一个成员有效(即只有100是有效的,其余成员会被覆盖)
union attr at = {100, 'k', 3.14};// 指定成员初始化:最后一个成员有效(即只有3.14是有效的,其余成员会被覆盖)
union attr at = {.x = 100,.y = 'k',.z = 3.14,
};
③ 访问(与结构体语法相同)
a.x = 10;
printf("%d\n", a.x); // 10
a.y = 'A'; // 覆盖 x 的低字节
printf("%d\n", a.x); // 10→被改成65('A'的ASCII)
④联合体指针
union attr *p = &at;
p->x = 100;
p->y = 'k';
p->z = 3.14; // 只有最后一个赋值的成员有效printf("%d\n", p->x);
printf("%c\n", p->y);
printf("%lf\n", p->z);
⑤联合体的使用
联合体一般很少单独使用,而经常以结构体的成员形式存在,用来表达某种互斥的属性
示例:
union attr
{int x;char y;double z;
};struct node
{int a;char b;double c;char Type ; // 用于标记当前类型,决定union attr 属性中那一部分生效union attr at; // at内有三种互斥的属性,非此即彼
};int main()
{struct node n;switch(n.Type){case 'A'//n.at.x // X 生效break ; case 'B'//n.at.y // y 生效break ; case 'C'//n.at.z // z 生效break ; }}
三、枚举:用单词代替魔法数字
枚举类型的本质是提供一种范围受限的整型,比如用0-6表示七种颜色,用0-3表示四种状态等,但枚举在C语言中并未实现其本来应有的效果,直到C++环境下枚举才拥有原本该有的属性。
- 声明枚举常量列表
enum
是关键字spectrum
是枚举常量列表标签,可以省略。省略的情况下无法定义枚举变量
示例:
enum spectrum{red, orange, yellow, green, blue, cyan, purple};
enum {reset, running, sleep, stop};
- 枚举变量
enum spectrum color = orange; // 等价于 color = 1
语法要点:
- 枚举常量实质上就是整型,首个枚举常量默认为0。
- 枚举常量在声明时可以赋值,若不赋值,则取其前面的枚举常量的值加1。
- C语言中,枚举等价于整型常量,支持整型数据的一切操作。
使用举例:
- 由于switch - case 中的case判断的数据只能接受整型常量,因此在很多情况下,只能使用数字,但是由于数字无法表述某种状态或选项,对读者并不友好,使用枚举可以借助一个单词来代表一个整型常量!!
switch(color)
{case red:// 处理红色...case orange:// 处理橙色...case yellow:// 处理黄色...
}
枚举数据最重要的作用,是使用有意义的单词,来替代无意义的数字,提高程序的可读性(方便对代码进行迭代更新)。
四、可移植对齐:联合体也会填充!
① 计算 M 值(最大成员对齐要求)
union U {char c; // m=1int i; // m=4
}; // M = max(1,4) = 4
② 压实联合体(无填充)
union __attribute__((packed)) RawReg {uint32_t word;uint8_t bytes[4];
}; // sizeof = 4(无填充)
③ 固定对齐
union Aligned64 {uint8_t buf[64];
} __attribute__((aligned(64))); // 地址必须是 64 倍数
五、位段(Bit-Field):极致压内存
① 语法
struct Flag {unsigned int enable : 1; // 占 1 bitunsigned int mode : 3; // 占 3 bitunsigned int rsv : 4; // 占 4 bit
}; // 总大小 = 1 byte
② 使用注意
- 不能超过底层类型宽度
- 位顺序未定义 → 仅适合同构系统通信
- 不能取地址 &flag.enable ❌