C/C++ 结构体内存对齐 四大核心原则(MSVC/GCC通用)
一、前置概念
- 对齐模数:每个类型自身的天然对齐值(等于该类型占用字节数)
char:1,short:2,int/float:4,long long/double:8,指针:8(64位)
- 对齐单位:默认等于结构体最大基础成员的对齐模数;也可通过
#pragma pack(n)手动指定n(1/2/4/8…) - 填充Padding:编译器自动插入空白字节,满足对齐规则,不存储数据
二、完整四条对齐规则(按执行顺序)
规则1:每个成员的起始地址,必须是「自身对齐模数」的整数倍
对结构体里任意成员T m:
m的起始地址 % sizeof(T) == 0
- 例:
int占4字节,只能放在地址0、4、8、12… - 例:
char占1字节,任意地址都合法,无限制
规则2:成员依次顺序摆放,不重排
成员按代码书写从上到下依次分配内存,不会自动调换顺序;
前一个成员结束后,若下一个成员地址不满足规则1,中间插入Padding空白。
规则3:结构体整体总大小,必须是「当前对齐单位」的整数倍
对齐单位 = min(最大成员天然对齐值, pack指定n)
结构体尾部不足的地方,编译器自动追加尾部Padding补齐。
规则4:#pragma pack(n) 手动压缩对齐(覆盖默认规则)
指定pack(n)后,每个成员的有效对齐模数 = min(类型自身大小, n)
#pragma pack(1):无任何填充,所有成员紧挨着存放;#pragma pack(2):int原本对齐4,现在对齐模数变为min(4,2)=2;- 使用完必须
#pragma pack()恢复默认对齐,避免影响其他结构体。
三、用你刚才的结构体举例实操验证
struct node
{
char a; // 1字节,对齐模数1
char b; // 1字节,对齐模数1
char c; // 1字节,对齐模数1
int s; // 4字节,对齐模数4
};
- 对齐单位:最大成员
int=4 - 内存分配:
- a:地址0(1字节)
- b:地址1(1字节)
- c:地址2(1字节)
- 下一个地址3,
int要求地址%4==0,不满足 → 插入1字节Padding(地址3) - s:地址4~7(4字节)
- 当前占用0~7共8字节,8是对齐单位4的整数倍,无需尾部填充
- 最终
sizeof(node)=8,和你运行结果一致
如果加 #pragma pack(1)
对齐单位=1,所有成员无需填充:
a(0),b(1),c(2),s(3~6),总大小7,7是1的倍数,无尾部Padding。
四、嵌套结构体对齐补充规则
如果结构体包含另一个结构体成员:
- 子结构体的对齐模数 = 子结构体自身整体对齐单位
- 父结构体的最大对齐单位,会同步更新为「所有基础类型、子结构体对齐模数」中的最大值
示例:
struct Sub{ char c; int i; }; // Sub大小8,对齐模数4
struct Test{
char ch; // 1字节
Sub subobj; // 子结构体,对齐模数4
};
- ch存地址0;地址1、2、3填充3字节;subobj从4开始;总大小12。
五、速记总结
- 每个变量起始地址,必须是自己大小的倍数;不够就中间填空;
- 成员顺序不变,从上往下排;
- 整个结构体总大小,必须是最大成员对齐值的倍数,不够末尾填空;
- pack(n)会限制所有类型最大对齐为n,可压缩内存。
#include <windows.h>
#include
using namespace std;
#pragma pack(16)
struct node
{
char a;
char b;
char c;
int s;
};
int main()
{
node s;
cout << sizeof(node) << endl;
cout << &s << endl;
cout << (void*)&(s.a) << endl;
cout << (void*)&(s.b) << endl;
cout << (void*)&(s.c) << endl;
cout << (void*)&(s.s) << endl;
return 0;
}
8
000000B7F038F740
000000B7F038F740
000000B7F038F741
000000B7F038F742
000000B7F038F744
D:\VSCODE\test\x64\Release\test.exe (进程 32880)已退出,代码为 0。
按任意键关闭此窗口. . .
为何还是8
核心原因:#pragma pack(n) 只是上限约束,不会强行扩大对齐
1. 先明确 pack 的核心公式
对任意成员类型 T:
该成员实际对齐模数 = min( sizeof(T), pack设置的n )
你写了 #pragma pack(16):
- char 占1字节:min(1,16) = 1
- int 占4字节:min(4,16) = 4
和默认无pack时的对齐模数完全一样,所以填充、总尺寸不会发生任何变化。
2. 一步步推演内存布局(和不加pack(16)完全相同)
struct node
{
char a; // 地址0,对齐1
char b; // 地址1,对齐1
char c; // 地址2,对齐1
int s; // 对齐模数4,要求起始地址 %4 == 0
};
- a(0)、b(1)、c(2) 占用前3字节;
- 下一个空闲地址是3,3%4≠0,插入1字节填充(地址3);
- int s 从地址4开始,占用4、5、6、7;
- 结构体总占用 0~7,一共8字节;
- 整体对齐单位 = min(最大成员4, pack16) =4,8是4的整数倍,无需尾部填充。
最终sizeof(node)=8。
3. pack(n) 两种效果区分
情况1:pack(n) ≥ 类型自身大小(你现在的场景 pack=16 ≥ 4)
pack 仅作为对齐上限,不会改变原有对齐规则,结构体大小不变。
#pragma pack(8)/#pragma pack(16)/ 不写pack,对这个结构体结果完全一致,都是8字节。
情况2:pack(n) < 类型自身大小(压缩对齐,才会改变尺寸)
举例 #pragma pack(1):
int对齐模数变为 min(4,1)=1,s可以直接放在地址3,无中间填充;
总字节:1+1+1+4 =7,sizeof(node)=7。
4. 补充:什么时候 pack(16) 才会生效?
只有结构体里存在大于4字节的成员(long long/double/指针,天然对齐8)时,pack(16)才会体现上限作用:
#pragma pack(16)
struct Test{
char ch;
long long ll; // 天然对齐8,min(8,16)=8
};
此时对齐模数仍为8;如果写 #pragma pack(4),long long对齐模数会被压缩为4。
5. 一句话总结
#pragma pack(16) 只是规定所有成员对齐不能超过16,但你的int本身只需要4字节对齐,上限16不会改变原有填充逻辑,因此结构体大小依旧是8。
想要改变尺寸,必须设置 pack(n) 小于成员天然对齐值(比如pack(1)、pack(2))。
1万+

被折叠的 条评论
为什么被折叠?



