- C++
共用体的内存对齐教程
- 2025-9-1 20:47:16 @
共用体的内存对齐教程
一、共用体(Union)基础回顾
在 C/C++ 中,共用体是一种特殊的数据结构,它允许多个不同类型的成员变量共享同一块内存空间,而非像结构体(Struct)那样为每个成员分配独立内存。这意味着:
- 共用体的所有成员从同一块内存的起始地址开始存储;
- 任意时刻只有一个成员能被有效访问(修改一个成员会覆盖其他成员的值);
- 共用体的大小由其“最大成员的大小”和“内存对齐规则”共同决定(核心考点)。
二、内存对齐的核心目的
内存对齐并非共用体特有,而是计算机硬件层面的优化规则,核心目的是提升 CPU 访问内存的效率:
- CPU 访问内存时,并非按“字节”逐个读取,而是按“对齐单位”(通常是 CPU 字长,如 4 字节、8 字节)批量读取;
- 若数据未对齐(如一个 4 字节的 int 存放在地址 1-4),CPU 需要读取 2 次内存并拼接数据,效率降低;
- 若数据对齐(如 int 存放在地址 0-3),CPU 仅需 1 次读取即可获取完整数据。
三、共用体内存对齐的 3 条核心规则
共用体的内存对齐需遵循以下 3 条规则,需结合实例理解:
规则 1:成员自身的对齐要求
每个成员变量都有其“自身对齐值”(即该类型的大小,如 char 为 1,int 为 4,double 为 8),成员必须存储在“自身对齐值的整数倍地址”上。
由于共用体所有成员共享起始地址,因此共用体的起始地址必须满足“所有成员中最大自身对齐值的整数倍”(否则最大成员无法对齐)。
规则 2:共用体整体大小的对齐要求
共用体的最终大小,必须是“所有成员中最大自身对齐值的整数倍”(称为“共用体对齐值”)。若最大成员的大小已满足该要求,则大小等于最大成员大小;若不满足,则需“补空字节”至对齐值的整数倍。
规则 3:成员共享内存,无偏移量
与结构体不同,共用体的成员无“偏移量”(结构体成员需按对齐规则依次偏移存储),所有成员的偏移量均为 0,直接覆盖同一块内存。
四、实例分析:手把手计算共用体大小
通过 3 个典型实例,掌握共用体内存对齐的计算方法:
实例 1:基础类型成员的共用体
#include <stdio.h>
// 定义共用体
union Test1 {
char a; // 自身对齐值 1,大小 1
int b; // 自身对齐值 4,大小 4
double c; // 自身对齐值 8,大小 8
};
int main() {
printf("union Test1 的大小:%zu\n", sizeof(union Test1));
return 0;
}
计算过程:
- 确定成员最大自身对齐值:
double c
的对齐值为 8(所有成员中最大); - 确定成员最大大小:
double c
的大小为 8; - 检查共用体大小是否满足对齐要求:8 是 8 的整数倍,无需补空;
- 最终大小:8 字节。
运行结果:union Test1 的大小:8
实例 2:包含数组成员的共用体
#include <stdio.h>
union Test2 {
char arr[5]; // 数组类型:自身对齐值 = 元素类型对齐值(char 为 1),大小 5
int x; // 自身对齐值 4,大小 4
};
计算过程:
- 确定成员最大自身对齐值:
int x
的对齐值为 4(arr
的对齐值为 1,更小); - 确定成员最大大小:
char arr[5]
的大小为 5(int x
为 4,更小); - 检查共用体大小是否满足对齐要求:5 不是 4 的整数倍,需补空至最近的 4 的整数倍(即 8);
- 最终大小:8 字节。
运行结果:union Test2 的大小:8
实例 3:包含结构体成员的共用体
#include <stdio.h>
// 先定义结构体(需先计算结构体大小和对齐值)
struct Sub {
char m; // 对齐值 1,大小 1
int n; // 对齐值 4,大小 4
};
// 定义包含结构体的共用体
union Test3 {
struct Sub s; // 结构体成员:自身对齐值 = 结构体的对齐值(4),大小 = 结构体大小(8)
double d; // 对齐值 8,大小 8
};
第一步:计算结构体 struct Sub
的大小(结构体对齐规则):
char m
占 1 字节,地址 0;int n
对齐值为 4,需存放在 4 的整数倍地址(地址 4-7),因此m
后补 3 字节空;- 结构体大小:1(m)+ 3(空)+ 4(n)= 8 字节,且 8 是最大对齐值 4 的整数倍,满足要求。
第二步:计算共用体 union Test3
的大小:
- 确定成员最大自身对齐值:
double d
的对齐值为 8(struct Sub
的对齐值为 4,更小); - 确定成员最大大小:
struct Sub s
和double d
的大小均为 8; - 检查对齐:8 是 8 的整数倍,无需补空;
- 最终大小:8 字节。
运行结果:union Test3 的大小:8
五、共用体与结构体内存对齐的核心区别
对比维度 | 共用体(Union) | 结构体(Struct) |
---|---|---|
内存分配方式 | 所有成员共享同一块内存,起始地址相同 | 为每个成员分配独立内存,按顺序偏移存储 |
成员偏移量 | 所有成员偏移量均为 0 | 成员偏移量需满足自身对齐值(可能有补空) |
大小计算依据 | 最大成员大小 + 对齐补空(满足最大对齐值) | 所有成员大小之和 + 偏移补空(满足最大对齐值) |
数据访问特性 | 同一时刻只能有效访问一个成员 | 可同时访问所有成员,互不干扰 |
六、常见问题与注意事项
-
“共用体大小 = 最大成员大小”一定成立吗?
不一定。只有当最大成员大小是“最大对齐值”的整数倍时才成立(如实例 1);若最大成员大小不满足对齐值(如实例 2 中最大成员大小 5 < 对齐值 4 的整数倍 8),则需补空,此时共用体大小 > 最大成员大小。 -
不同编译器的对齐规则是否一致?
基本规则一致,但默认对齐值可通过编译器指令修改(如 GCC 中的__attribute__((aligned(n)))
、MSVC 中的#pragma pack(n)
)。例如,若设置#pragma pack(2)
,则实例 2 中union Test2
的最大对齐值变为 2,大小为 6(5 补空至 6,6 是 2 的整数倍)。 -
共用体成员的地址是否相同?
是的。由于所有成员共享起始地址,&union_var.a == &union_var.b == &union_var
(以实例 1 为例)。
七、练习题(附答案)
练习 1:计算以下共用体的大小
union Ex1 {
short s; // 大小 2,对齐值 2
char c[3]; // 大小 3,对齐值 1
long l; // 大小 4,对齐值 4
};
答案:4 字节
解析:最大对齐值为 4,最大成员大小为 4,4 是 4 的整数倍,无需补空。
练习 2:计算以下共用体的大小(假设编译器默认对齐值为 8)
struct Inner {
float f; // 大小 4,对齐值 4
char ch; // 大小 1,对齐值 1
};
union Ex2 {
struct Inner in; // 结构体大小?对齐值?
long long ll; // 大小 8,对齐值 8
};
答案:8 字节
解析:
- 先算
struct Inner
:float f
占 0-3 字节,char ch
占 4 字节,大小 5;最大对齐值为 4,需补空至 8(4 的整数倍),因此结构体大小为 8,对齐值为 4。 - 共用体最大对齐值为 8(
long long ll
),最大成员大小为 8,满足对齐,最终大小 8。