外观
02.结构体内存对齐计算(含#pragma pack)
约 2230 字大约 7 分钟
宏处理算法C个人
2025-12-10
一、先把"对齐值"讲清楚
结构体内存对齐里最容易混的词是 对齐值。
可以把它理解成一句话:
某个成员的起始地址,必须是几的倍数。
比如在常见平台上:
char 对齐值 1:可以从任意地址开始放
short 对齐值 2:起始地址必须是 2 的倍数
int 对齐值 4:起始地址必须是 4 的倍数如果 int 的对齐值是 4,那么它适合放在:
0x1000
0x1004
0x1008
0x100C不适合直接放在:
0x1001
0x1002
0x1003所以结构体里出现填充字节,本质就是编译器在帮成员找到满足对齐要求的位置。
二、推荐记忆法:先看门槛,再排座位,最后补车厢
不要先背复杂公式,先记这个口诀:
看类型定门槛;offset 不整除就补;放完加大小;最后补到最大门槛倍数。
这里的"门槛"就是成员的实际对齐值。
2.1 第一步:先看每个成员的实际门槛
如果没有 #pragma pack:
成员实际门槛 = 成员类型的自然对齐值如果有 #pragma pack(n):
成员实际门槛 = min(成员类型的自然对齐值, n)注意:这里是 自然对齐值,不是简单的"成员大小"。
很多基础类型的大小和自然对齐值刚好相同,所以容易误以为"对齐值 = sizeof(成员)"。但这个说法不够严谨:
- 数组成员的对齐值看元素类型,不看数组总大小。
- 嵌套结构体成员的对齐值看内部结构体的整体对齐值。
- 某些平台上
double的大小是 8,但自然对齐值可能不是 8。
2.2 第二步:顺序排座位
从 offset = 0 开始,一个成员一个成员往后放。
每放一个成员前,先问一句:
当前 offset 能不能整除这个成员的实际门槛?
如果能,直接放。
如果不能,先补 padding,补到能整除为止。
放完以后:
offset += sizeof(成员)2.3 第三步:最后补车厢
所有成员放完后,结构体整体大小还要补齐。
结构体整体门槛 = 所有成员实际门槛中的最大值最终:
sizeof(结构体) 必须是结构体整体门槛的整数倍如果当前大小不是整数倍,就在结构体末尾继续补 padding。
三、基础示例:默认对齐
struct A {
char c;
int i;
short s;
};假设常见平台:
char 大小1,门槛1
int 大小4,门槛4
short 大小2,门槛2计算:
offset 0:放 char c,门槛1,0能整除1,直接放
c 占1字节,offset = 1
offset 1:准备放 int i,门槛4,1不能整除4
补3字节 padding,offset = 4
i 占4字节,offset = 8
offset 8:准备放 short s,门槛2,8能整除2,直接放
s 占2字节,offset = 10
结构体整体门槛 = max(1,4,2) = 4
当前大小10不是4的倍数,末尾补2字节
最终 sizeof(struct A) = 12布局:
offset 0 : c
offset 1-3 : padding
offset 4-7 : i
offset 8-9 : s
offset 10-11: padding四、#pragma pack 的理解
#pragma pack(n) 可以理解成:
把成员的最大门槛限制为 n。
它只会压低门槛,不会抬高门槛。
比如:
#pragma pack(4)
struct B {
char c;
double d;
int i;
};
#pragma pack()假设自然对齐值:
char 自然门槛1
double 自然门槛8
int 自然门槛4使用 pack(4) 后:
char 实际门槛 min(1,4) = 1
double 实际门槛 min(8,4) = 4
int 实际门槛 min(4,4) = 4计算:
offset 0:放 char c,占1,offset = 1
offset 1:放 double d,门槛4,1不能整除4
补3字节,offset = 4
d 占8字节,offset = 12
offset 12:放 int i,门槛4,12能整除4
i 占4字节,offset = 16
整体门槛 = max(1,4,4) = 4
16是4的倍数
最终 sizeof(struct B) = 16如果没有 pack(4),double 通常按 8 对齐,布局可能变成:
offset 0:c,占1
offset 1-7:padding
offset 8-15:d
offset 16-19:i
offset 20-23:末尾 padding
sizeof = 24五、最容易错的几个点
5.1 数组成员:看元素门槛,不看数组总大小
struct C {
char arr[10];
int i;
};arr 的大小是 10,但它的门槛是 1,因为元素类型是 char。
计算:
offset 0:arr[10],门槛1,占10,offset = 10
offset 10:int i,门槛4,10不能整除4
补2字节,offset = 12
i 占4字节,offset = 16
整体门槛 = 4
最终 sizeof = 16不要把 char arr[10] 当成"门槛10"。
5.2 成员顺序会影响结构体大小
struct Bad {
char c1;
int i;
char c2;
};常见结果:
c1 占1
补3
i 占4
c2 占1
末尾补3
sizeof(struct Bad) = 12换个顺序:
struct Good {
int i;
char c1;
char c2;
};常见结果:
i 占4
c1 占1
c2 占1
末尾补2
sizeof(struct Good) = 8所以设计结构体时,通常把大门槛成员放前面,可以减少 padding。
5.3 嵌套结构体:先单独算,再当成一个整体成员
struct Inner {
char c;
int i;
};
struct Outer {
char x;
struct Inner in;
};先算 Inner:
char c 占1
补3
int i 占4
sizeof(struct Inner) = 8
Inner 的整体门槛 = 4再算 Outer:
offset 0:char x,占1,offset = 1
offset 1:准备放 struct Inner in
in 的门槛是4,1不能整除4
补3字节,offset = 4
in 整体占8字节,offset = 12
Outer 整体门槛 = 4
12是4的倍数
sizeof(struct Outer) = 12注意:不能把 Outer 简单打散成:
struct Fake {
char x;
char c;
int i;
};这样会破坏 Inner 作为整体成员时的对齐要求。
正确口诀:
嵌套结构体先单独算;外层把它当一个成员;大小取内部结构体的
sizeof;门槛取内部结构体的整体对齐值。
5.4 pack 只压低门槛,不改变成员大小
#pragma pack(2)
struct D {
char c;
int i;
};
#pragma pack()int 的大小仍然是 4,只是实际门槛从 4 被压到 2。
计算:
offset 0:char c,占1,offset = 1
offset 1:int i,门槛2,1不能整除2
补1字节,offset = 2
i 占4字节,offset = 6
整体门槛 = 2
最终 sizeof = 6六、快速参考表
不同平台和编译器可能有差异,下面是常见情况:
| 类型 | 常见大小 | 常见自然对齐值 |
|---|---|---|
char | 1 | 1 |
short | 2 | 2 |
int | 4 | 4 |
float | 4 | 4 |
double | 8 | 8 或 4,取决于平台 |
| 指针 | 32位为4,64位为8 | 通常等于指针大小 |
| 数组 | 元素大小 * 元素个数 | 取决于元素类型 |
| 结构体 | 包含内部 padding 后的总大小 | 内部成员实际门槛的最大值 |
七、验证工具:sizeof + offsetof
结构体对齐最好的学习方法是:
先手算,再用
sizeof和offsetof验证。
示例:
#include <stdio.h>
#include <stddef.h>
#pragma pack(push, 4)
struct Test {
char a;
int b;
double c;
short d;
};
#pragma pack(pop)
int main(void)
{
printf("sizeof(struct Test) = %zu\n", sizeof(struct Test));
printf("offsetof(a) = %zu\n", offsetof(struct Test, a));
printf("offsetof(b) = %zu\n", offsetof(struct Test, b));
printf("offsetof(c) = %zu\n", offsetof(struct Test, c));
printf("offsetof(d) = %zu\n", offsetof(struct Test, d));
return 0;
}在常见环境下,按 pack(4) 计算:
a:offset 0,占1
补3
b:offset 4,占4
c:offset 8,占8,pack(4) 后只需 4 对齐
d:offset 16,占2
当前大小18,整体门槛4,末尾补2
sizeof(struct Test) = 20八、位域和可移植性提醒
位域的布局规则比较复杂,和编译器、目标平台、底层类型、大小端等都有关系。
struct BitField {
unsigned int a : 4;
unsigned int b : 8;
unsigned int c : 20;
};学习普通结构体对齐时,可以先不要把位域混进来。
工程中如果涉及协议、文件格式或跨平台二进制布局,建议:
- 使用
sizeof和offsetof验证。 - 使用静态断言锁定结构体大小。
- 谨慎使用
#pragma pack(1),它可能降低访问效率,某些嵌入式平台还可能导致非对齐访问异常。
C11 静态断言示例:
#include <assert.h>
static_assert(sizeof(struct Test) == 20, "struct Test size mismatch");九、最终口诀
结构体对齐只记这一套:
看类型定门槛;offset 不整除就补;放完加大小;最后补到最大门槛倍数。
再补三个关键点:
#pragma pack(n)是限制器:实际门槛 =min(自然对齐值, n)。- 数组成员看元素门槛,不看数组总大小。
- 嵌套结构体先单独算,外层把它当成一个整体成员。
