【C++】字节对齐—alignof/alignas和#pragma pack(n)
0. 为什么要字节对齐
(1)是什么?
每个变量(结构体的成员)都有“摆放要求”:“我必须放在 编号是 N 的倍数 的货架格子上。”。N 就是它的 对齐值 alignof(T)。
//字节自然对齐时
//int x 对齐到 4,放在 4~7:
[ 第0排 ] [ 第1排 ]
0 1 2 3 | 4 5 6 7 | 8 9 10 11
↑___________↑
一次就能搬走(4 字节)
//叉车(CPU)一趟就把 4 5 6 7 整排端走,不横跨。
//字节没有对齐时
//把同一个 int 故意放到 3~6:
[ 第0排 ] [ 1 排 ]
0 1 2 3 | 4 5 6 7 | 8 9 10 11
↑_______↑
3 4 5 6 ← 这个 int 占用两排
//第 0 排里有 3 号字节
//第 1 排里有 4 5 6 号字节
//叉车一次只能搬一排,于是:
//先搬第 0 排,把 3 号字节捡起来;
//再搬第 1 排,把 4 5 6 捡起来;
//最后还要在 CPU 内部把碎片拼成完整的 4 字节。
//CPU取两次,性能减半。
(2)为什么要有这条规则?
因为CPU一次只能搬 4、8、16……字节的作为一个单元货;
如果结构体的存储横跨两个单元,CPU要取两次,甚至直接翻车(硬件异常)。
所以需要内存对齐。
(3)自然字节对齐
没有显式通过 alignas 修改的情况下,结构体的对齐方式是由其 【最大对齐成员】(alignof) 决定的。
struct MyStruct1
{
char a; // 1 byte
int b; // 4 byte
double c; // 8 byte
};
//最大对齐成员是 double c,对齐为 8 字节。
//所以 alignof(MyStruct1) 是 8。
//编译器会自动插入填充字节,使得整个结构体大小为 16 字节(不是 32)。
(4)sizeof(T)最终长度
- 无论自然对齐还是
alignas(N),最终大小一定是其对齐值的整数倍,并且是在“刚好能装下所有成员”的前提下最小的那个倍数。 - 自然对齐同样满足这句话,只不过把 N 换成“该类型自己的自然对齐值”而已。
- 自然对齐时,编译器在【成员之间或结构体末尾】插入若干字节,目的:使下一个成员/数组元素满足对齐要求。
- alignas(N) 时,【只在末尾】再补 0…N−1 字节,让 sizeof(T) 升为 N 的倍数。
1. alignof和alignas
alignas 和 alignof 是 C++11 引入的两个关键字,用于控制变量(如果是结构体的话,就是【整个结构体起始地址】)的内存对齐。
对齐值必须是2的幂。
(1)alignof
作用:查询类型(结构体的话,是其最大成员)的自然对齐对齐值。
语法:alignof(Type)
返回值:std::size_t,表示该类型必须对齐到的字节边界。
#include <iostream>
#include <cstddef>
int main()
{
std::cout << "alignof(int) = " << alignof(int) << '\n';
std::cout << "alignof(double) = " << alignof(double) << '\n';
struct MyStruct1
{
char a;
int b;
};
std::cout << "sizeof(MyStruct1)=" << sizeof(MyStruct1) << "\n"; //最大成员b的内存对齐为4
struct MyStruct2
{
char a;
int b;
double c;
};
std::cout << "sizeof(MyStruct2)=" << sizeof(MyStruct2) << "\n"; //最大成员c的内存对齐为8
}
(2)alignas
作用:显式修改某个变量的对齐值(结构体的话,结构体整体的起始地址),按指定字节数对齐(也会影响sizeof)。。
语法:alignas(N),其中 N 必须是 2 的幂(如 1, 2, 4, 8, 16...),CPU读取的小单元字节数。
用途:优化性能、满足硬件要求、与底层接口对齐等。
#include <iostream>
#include <cstddef>
//自然对齐
struct MyStruct1
{
char a;
int b;
double c;
};
//MyStruct2整体必须从32字节倍数的内存开始
struct alignas(32) MyStruct2
{
char a;
int b;
};
int main()
{
MyStruct1 s1;
std::cout << "sizeof(MyStruct1)=" << sizeof(MyStruct1) << "\n";
std::cout << "alignof(MyStruct1) = " << alignof(MyStruct1) << '\n';
std::cout << "address of s1 = " << &s1 << '\n';
MyStruct2 s2;
std::cout << "sizeof(MyStruct2)=" << sizeof(MyStruct2) << "\n";
std::cout << "alignof(MyStruct2) = " << alignof(MyStruct2) << '\n';
std::cout << "address of s2 = " << &s2 << '\n';
}
注意:实际是设定小单元的字节数,变量起始地址对齐到N时,编译器会在【结构体末尾】补 0~N-1 字节,使得 sizeof(T) = ≥ 实际成员大小的最小 N 的倍数,但不是简单地就等于 N
#include <iostream>
#include <cstddef>
struct alignas(32) S { char c[48]; };
int main()
{
std::cout << sizeof(S) << '\n'; // 输出 64
std::cout << alignof(S) << '\n'; // 输出 32
}
//成员实际占用:48 字节
//对齐要求:32 字节
//48 ÷ 32 = 1 余 16 → 不够整除
//下一个 32 的倍数:64
//所以编译器在末尾再补 16 字节
| 原大小 | alignas(N) | 最终 sizeof |
|---|---|---|
| 48 | 32 | 64 |
| 31 | 32 | 32 |
| 32 | 32 | 32 |
| 33 | 32 | 64 |
2. pragma pack(n)
预处理器指令,显式控制编译器对齐策略。
强制将结构体成员按照指定字节数对齐。
pragma pack()省内部空间,牺牲性能。网络协议、文件头常用 pack(1) 或 pack(2)
(1) 和alignof、alignas的区别:
- alignof/alignas是C++11起的标准。而pragma pack(n)是编译器的打包控制。
- alignas控制:整个结构体的起始地址对齐, 不改变成员间布局。
pragma pack控制:成员间的填充(padding),会改变成员布局。
(2)举例说明
pragma pack(n):强行降低“包裹”摆放要求。
n 越小,越紧凑,空间省,可能未对齐访问。
下面把
#pragma pack(push, 1)
struct FooPacked
{
char c; // 1
int i; // 4
double d; // 8
};
#pragma pack(pop)
在内存里 逐字节 画出来,一看就懂“pack(1) 把填充全部挤掉”长什么样
- “自然对齐”长什么样(无 pack)
偏移 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
|c|x|x|x|i|i|i|i|x|x|x|x|x|x|x|x|d|...(再 8 字节)
1 3 废 4 8 废(对齐到 8)→ 共 24 字节
alignof = 8sizeof = 24
- 加上 #pragma pack(push, 1) 之后
规则:所有成员只按 1 字节对齐,中间和尾部都不插废字节。
偏移 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
|c|i|i|i|i|d|d|d|d|d|d|d|d|
1 4 8
- 没有任何“x”废字节
- 总大小 = 1 + 4 + 8 = 13 字节
alignof(FooPacked) = 1- 如果放在数组里,下一个元素直接从偏移 13 开始,不一定落在 8 的倍数,
访问d时可能横跨两个 8 字节缓存行,甚至触发总线错误(ARM/MIPS)。

浙公网安备 33010602011771号