用 sizeof(结构体名) 能问出它占多少字节——但答案往往比"成员大小相加"更大,因为里面藏着看不见的空白。这空白叫内存对齐填充,由两条规则决定:① 每个成员必须从"自己大小的整数倍"位置开始;② 整个结构体的大小必须是"最宽成员"的整数倍。把成员按大小从大到小排列,往往能省下几个字节。
8.1.1~8.1.4 我们一路都在说"结构体是一块连续内存"。这一节真正掀开这块内存看排布。它把好几个旧知识点串了起来:2.4 的"类型决定占多大"、3.1.7 的 sizeof、7.1.1 的"sizeof(a)/sizeof(a[0]) 数格子"、7.4.3 的"指针 +1 跳几个字节"——指针跳多远,就是靠这一节讲的"结构体大小"算出来的(8.4.2 会用到)。
一、先看一个"对不上"的例子
struct A {
char c; // 1 字节
int i; // 4 字节
};
cout << sizeof(A) << endl; // 输出 8,不是 5
1 + 4 = 5,可 sizeof 说 8。多出来的 3 个字节去哪了?
它们被"垫"进去了——不是你的数据,是为了让 i 能站在一个"整齐"的位置上而空出来的空白。
二、两条规则(背下来就不怕算错)
| 规则 | 内容 | 后果 |
|---|---|---|
| 规则 1(成员各自对齐) | 每个成员的起始位置必须是它自身大小的整数倍 | 不够就在它前面垫空白 |
| 规则 2(整体对齐) | 结构体总大小必须是最宽成员大小的整数倍 | 不够就在末尾垫空白 |
"垫空白"就是浪费,但这是 C++ 的默认行为,编译器不会为省这点空间去牺牲速度(原因见第五节)。
三、逐字节排队演算
例 1:struct A { char c; int i; };
| 位置 | 放什么 | 说明 |
|---|---|---|
| 0 | c |
1 字节 |
| 1~3 | 空白(padding) | i 是 int,要求起点是 4 的倍数 → 下一站是 4 |
| 4~7 | i |
4 字节 |
总用量 8,是 4 的倍数 → sizeof(A) = 8 ✅
例 2:struct B { char c; short s; int i; };
| 位置 | 放什么 |
|---|---|
| 0 | c |
| 1 | 空白(s 要求 2 的倍数) |
| 2~3 | s |
| 4~7 | i(4 的倍数,正好) |
总用量 8 → sizeof(B) = 8 ✅(同样是 8,但只浪费了 1 个字节)
例 3:struct C { char c; int i; short s; };
| 位置 | 放什么 |
|---|---|
| 0 | c |
| 1~3 | 空白(i 要 4 的倍数) |
| 4~7 | i |
| 8~9 | s |
| 10~11 | 末尾空白(总用量 10,但整体要凑成 4 的倍数 → 12) |
sizeof(C) = 12 ✅——同样的三个成员,只因为换了顺序,就从 8 变成 12。
四、所以:换顺序能省空间
把例 3 改成"从大到小"排:
struct C1 { char c; int i; short s; }; // 12 字节
struct C2 { int i; short s; char c; }; // 8 字节
| 位置 | C2 里放什么 |
|---|---|
| 0~3 | i |
| 4~5 | s |
| 6 | c |
| 7 | 末尾空白(凑成 4 的倍数) |
省下 4 个字节。一个结构体省 4 字节不起眼,但 8.4 的"结构体数组"里乘以 1000 个元素,就是 4000 字节。
实用口诀:成员按大小从大到小排(或用
char收尾),填充最少。 教材里的Student之所以排成char[20] → int → double,也是这个道理(具体见下)。
五、为什么要有对齐?
因为 CPU 不是按字节读内存的,而是按"字长"整块读的(32 位机器一次读 4 字节,64 位读 8 字节)。
// 假设 i 从位置 2 开始(未对齐)
// 位置 2~5 是一个 int
// CPU 读一次只能覆盖 0~3 或 4~7 —— 两次都装不下它
// → 必须读两次(0~3 和 4~7),再各自切出 2 个字节拼起来
对齐 = 让每个成员都完整地落在某一次"整块读取"里,一次就拿到。这是拿内存空间换 CPU 时间——而内存便宜、时间贵,所以 C++ 默认选择对齐。
六、三个常见大小的快速估算
| 结构体 | 成员相加 | 实际 | 为什么 |
|---|---|---|---|
struct A { char c; int i; }; |
5 | 8 | i 前垫 3 |
struct C { char c; int i; short s; }; |
7 | 12 | i 前垫 3 + 末尾垫 2 |
struct Student { char name[20]; int age; double score; }; |
32 | 32 | 20(age 起点 20,是 4 的倍数 ✓);24(score 起点 24,是 8 的倍数 ✓);总 32,是 8 的倍数 ✓——正好,一个空白都不多 |
数组作成员时按"元素类型"对齐:
char name[20]按char的规则(对齐 1)——它只看元素是char,不看有 20 个。这是最容易算错的一点。
七、动手 Demo:把每个结构体的真实大小打印出来
#include <iostream>
using namespace std;
struct A { char c; int i; }; // 1 + 4
struct B { char c; short s; int i; }; // 1 + 2 + 4
struct C { char c; int i; short s; }; // 1 + 4 + 2
struct D { int i; short s; char c; }; // 4 + 2 + 1(从大到小)
struct Student {
char name[20];
int age;
double score;
};
int main() {
cout << "A(char + int) = " << sizeof(A) << " (相加 5)" << endl;
cout << "B(char + short + int) = " << sizeof(B) << " (相加 7)" << endl;
cout << "C(char + int + short) = " << sizeof(C) << " (相加 7)" << endl;
cout << "D(int + short + char) = " << sizeof(D) << " (相加 7)" << endl;
cout << "Student = " << sizeof(Student) << endl;
return 0;
}
看到什么算成功:
A(char + int) = 8 (相加 5)
B(char + short + int) = 8 (相加 7)
C(char + int + short) = 12 (相加 7)
D(int + short + char) = 8 (相加 7)
Student = 32
三个看点:
- A 相加 5、实际 8——垫了 3 个空白;
- B 和 C 成员完全一样(只是个顺序不同),一个 8 一个 12——顺序真的影响大小;
- D 就是 C 从大到小重排,立刻省下 4 字节。
30 秒实验:在 C 后面加一句 char d;,猜猜变成多少,再跑一遍验证。你会发现它可能一点都不变(14 → 凑到 16 之前,d 正好填进了原来的末尾空白里)——"加了个成员,大小没变" 是对齐最反直觉的一幕。
八、这件事在实际中有什么用
- 算结构体数组的总内存:
sizeof(Student) * 1000才是真占用,不能拿"成员相加 × 1000"估(8.4)。 - 指针跳动的单位:
p + 1在Student *p上跳的是sizeof(Student)字节(7.4.3 的规则)——跳过 32 字节,不是跳过 5+4+8。 - 文件读写/网络传输:结构体的字节数和字段位置必须双方约定一致(这也是为什么"别把结构体直接写进文件再换编译器读")。
- 笔试面试高频题:
sizeof算结构体大小,几乎必考。
九、常见误区
- "结构体大小 = 成员大小相加"——错的,还要加上对齐填充。这是本节最核心的一条。
- "
char name[20]按 20 对齐"——按元素类型对齐,char就是 1。看元素类型,不看元素个数。 - "成员顺序随便,反正编译器会优化"——编译器不会替你重排成员(语言规定成员顺序就是声明顺序)。想省空间只能自己排。
- "填充字节里是 0"——不保证。跟未初始化变量一样,里面的内容是垃圾值(有的调试器会刷成特定值,别依赖)。所以不要整体比较内存来判断两个结构体是否相等,要逐成员比较。
- "那用
#pragma pack(1)压掉填充就好了"——它能取消填充(笔试和嵌入式场合常见),代价是访问变慢、某些平台直接崩。学习阶段不用碰它,知道这回事就行。
自测三问(点开对答案)
1. `struct A { char c; int i; };` 的 `sizeof` 是多少?为什么不是 5?——8。因为 `int` 必须从 4 的倍数位置开始,`c` 占 0 之后要垫 3 个字节到位置 4。 2. 对齐的两条规则是什么?——① 每个成员的起始位置是**自身大小**的整数倍;② 结构体总大小是**最宽成员大小**的整数倍。 3. `struct C1 { char c; int i; short s; };` 和 `struct C2 { int i; short s; char c; };` 谁更小?差多少?——`C1` 是 12,`C2` 是 8,**差 4 字节**。按大小从大到小排更省。- ☐ 8.2 重命名数据类型(下一节)
相关笔记
- 结构体的嵌套 —— 上一节:嵌套结构体
- 重命名数据类型 —— 下一节:给类型起个短名
- sizeof运算符 —— 量大小的工具
- 指针运算 —— 指针 +1 跳多少,靠的就是这个大小
- 🔷 C++基础 · 索引 —— 全课路线图