结构体对齐

C/C++ 结构体内存对齐 四大核心原则(MSVC/GCC通用)

一、前置概念

  1. 对齐模数:每个类型自身的天然对齐值(等于该类型占用字节数)
    • char:1,short:2,int/float:4,long long/double:8,指针:8(64位)
  2. 对齐单位:默认等于结构体最大基础成员的对齐模数;也可通过 #pragma pack(n) 手动指定n(1/2/4/8…)
  3. 填充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
};
  1. 对齐单位:最大成员int=4
  2. 内存分配:
    • a:地址0(1字节)
    • b:地址1(1字节)
    • c:地址2(1字节)
    • 下一个地址3,int要求地址%4==0,不满足 → 插入1字节Padding(地址3)
    • s:地址4~7(4字节)
  3. 当前占用0~7共8字节,8是对齐单位4的整数倍,无需尾部填充
  4. 最终sizeof(node)=8,和你运行结果一致

如果加 #pragma pack(1)

对齐单位=1,所有成员无需填充:
a(0),b(1),c(2),s(3~6),总大小7,7是1的倍数,无尾部Padding。


四、嵌套结构体对齐补充规则

如果结构体包含另一个结构体成员:

  1. 子结构体的对齐模数 = 子结构体自身整体对齐单位
  2. 父结构体的最大对齐单位,会同步更新为「所有基础类型、子结构体对齐模数」中的最大值

示例:

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。

五、速记总结

  1. 每个变量起始地址,必须是自己大小的倍数;不够就中间填空;
  2. 成员顺序不变,从上往下排;
  3. 整个结构体总大小,必须是最大成员对齐值的倍数,不够末尾填空;
  4. 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
};
  1. a(0)、b(1)、c(2) 占用前3字节;
  2. 下一个空闲地址是3,3%4≠0,插入1字节填充(地址3);
  3. int s 从地址4开始,占用4、5、6、7;
  4. 结构体总占用 0~7,一共8字节;
  5. 整体对齐单位 = 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))。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值