进程内存布局:你的数据住在哪
栈、堆、全局区……各放什么、谁负责回收?
商用重叠
◎学完你会
- 说清栈 / 堆 / 全局静态区 / 常量区 / 代码段各放什么、谁负责回收;
- 区分"栈自动回收、堆需手动"以及它们的地址高低;
- 理解内存对齐与
sizeof 的关系,以及深递归为什么会栈溢出。
1内存布局分区图:点开看每段放什么、谁回收
程序运行时内存大致分五段(地址从高到低)。点每一段看它装什么、由谁负责回收。
栈 地址较高 · 向下生长
堆 地址较低 · 向上生长
全局 / 静态区
常量区(只读)
代码段(只读) 地址最低
口诀:栈高堆低;局部变量在栈、new 的在堆;全局/静态在中间;常量、代码只读。
点左边任一段。
👉 谁负责回收:栈——函数返回自动回收;堆——必须手动 delete;全局/静态、常量、代码段——程序结束才回收。
2关键命令
| 写法 | 含义 |
&局部变量 | 打印栈上地址(通常较高,如 0x7ff...c) |
new int | 在堆上申请内存(地址通常较低),返回指针 |
sizeof(结构体) | 看结构体实际占多少字节(含对齐填充) |
✅ 想亲眼验证:打印 &局部变量 和 new int 的地址,能直观看到"栈高堆低"。
3栈自动回收,堆必须手动
这是 C++ 内存管理最核心的区别。
void f(){
int a = 5; // 栈上:函数返回,a 自动消失
int* p = new int(5); // 堆上:不 delete,就一直占着
delete p; // 必须手动还回去
}
为什么堆要小心:new 拿到的内存不会自己消失。函数返回时,指针变量 p 没了(栈回收),但它指向的堆内存仍占着——谁都不管,就是内存泄漏。这就是后面 delete 和智能指针要解决的事。
4内存对齐与 sizeof:成员之和 ≠ sizeof
为了 CPU 高效读取,结构体成员之间可能塞入"填充字节",所以 sizeof 常大于成员大小之和。
struct S { char c; int i; };
// sizeof(S) 往往是 8,不是 1+4=5
// c 占 1 字节,后补 3 字节对齐到 4,i 占 4 字节 → 共 8
易错:以为 sizeof(结构体) 等于成员大小之和。实际要算对齐填充。想少占内存,把大的成员排前面、小的排后面,可减少填充。
!易错点
① 返回局部变量的地址/引用——局部变量在栈上,函数一返回就没了,返回它的地址得到悬垂指针(指向已回收的内存)。
② 大数组放栈导致栈溢出——栈空间有限(通常几 MB),几百万元素的数组应放堆或全局。
③ 以为 sizeof 等于成员之和——忽略了对齐填充,算出的字节数不对。
④ 深递归栈溢出——每层递归都在栈上压帧,递归太深把栈撑爆 → 栈溢出(Segmentation fault)。
?跨学科:内存五段,就像一栋楼的五层
栈像"临时休息区":人来(进函数)占个位置,人走(函数返回)自动腾空——不需要你收拾。堆像"自助储物柜":你自己打开柜门存东西(new),离开前必须自己锁好柜门(delete),忘了锁就是泄漏。
全局/静态区像"公司档案室":程序一开工就建好,一直放到下班才归档。常量区、代码段像"消防手册":只读,贴墙上不许改。分清"谁收拾",就不会把堆上的东西丢在栈上乱放。
✎练一练
函数里的普通局部变量(如 int a = 5;)存在哪、由谁负责回收?
局部变量在栈上,函数返回时由栈机制自动回收,不需要也不允许手动 delete。
在函数里声明一个超大数组(如几百万个 int)会发生什么?
栈空间有限(通常几 MB),超大局部数组瞬间把栈撑爆 → 栈溢出。应放堆(new/vector)或全局。
struct S { char c; int i; }; 的 sizeof(S) 通常是?
c 占 1 字节后,为让 i 按 4 对齐会补 3 字节填充 → 8。sizeof 常大于成员之和,别忽略对齐填充。