一、C++ 内存区域整体划分(经典五区)
cpp
┌───────────────┐
│ 栈 (Stack) │ ← 函数调用、局部变量
├───────────────┤
│ 堆 (Heap) │ ← new / malloc
├───────────────┤
│ 全局/静态区 │ ← global / static
├───────────────┤
│ 常量区 │ ← 字符串字面量、const
├───────────────┤
│ 代码区 │ ← 程序指令
└───────────────┘
1. 高频坑点
- 仅堆内存会发生「内存泄漏」:栈和静态区由编译器 / 操作系统自动管理,不存在泄漏问题;
- 栈溢出 ≠ 内存泄漏:栈溢出是栈空间被耗尽(如递归过深),泄漏是堆内存未手动释放;
- 内存泄漏的核心是「可访问性丢失」:堆内存被分配后,失去了所有指向它的指针,既无法使用也无法释放,并非单纯的 "忘记释放"。
2. 栈(stack)
- 由编译器进行管理,在需要时由编译器自动分配空间 ,在不需要时候自动回收空间,一般保存的是局部变量和函数参数等。
- 大多数编译器中,参数是从右向左入栈 ,函数调用完成后,局部变量先出栈,然后是参数,最后是栈顶指针最开始存放的地址 ,程序
由该点继续运行,不会产生碎片。 - 栈是高地址向低地址扩展,空间较小
bash
高地址
|
| [栈底] ← 初始位置(固定)
| ↓
| [数据]
| ↓
| [栈顶] ← 动态移动(随压栈/出栈变化)
|
低地址
3. 堆(heap)
- 由程员手动管理(newmallocdeletefree)进行分配和回收,如果不进行回收的话,会造成内存泄漏的问题。
- 不连续的空间,实际上系统中有一个空闲链表,当有程序申请的时候,系统遍历空闲链表找到第一个大于等于申请大小的空间分配给程序 ,如果有剩余的,也会将剩余的插入到空闲链表中,这也是产生内存碎片的原因。
- 堆是低地址向高地址扩展,空间较大,较为灵活。
bash
高地址
|
| [栈底] ← 栈从高地址开始,向低地址扩展
| ↓
| [栈顶]
|
| (空闲内存区域)
|
| [堆末尾] ← 堆从低地址开始,向高地址扩展
| ↑
| [堆起始地址]
低地址
4. 静态区(static/global 区)
特点:
1.程序启动时分配
2.程序结束时释放
3.整个程序生命周期
存什么:
cpp
int g = 10; // 全局变量
static int s = 20; // 静态变量
void foo() {
static int x = 0; // 局部静态变量
}
| 对比点 | 栈 | 堆 | 静态区 |
|---|---|---|---|
| 分配方式 | 自动 | 手动 | 自动 |
| 释放方式 | 自动 | 手动 | 程序结束 |
| 生命周期 | 函数级 | 程序员控制 | 程序级 |
| 大小 | 小 | 大 | 中 |
| 速度 | 高 | 低 | 中 |
| 泄漏风险 | 无 | 高 | 低 |
二、内存泄漏的本质与核心原因(面试必讲)
1. 内存泄漏的标准定义
堆内存泄漏:程序员通过new/malloc在堆上分配了内存,后续既没有执行delete/free释放,又失去了所有指向该堆内存的指针,导致这块内存成为「不可访问的垃圾内存」------ 程序运行期间无法再使用,也无法释放,直到程序退出后由操作系统回收。
cpp
//new 了不 delete
void foo() {
int* p = new int(10);
} // 忘记 delete
//指针被覆盖
int* p = new int(10);
p = new int(20); // 原内存丢失
//️ new[] / delete 不匹配
int* arr = new int[10];
delete arr; // 应该 delete[]
️//循环容器中持续 new
for (;;) {
new int(1);
}
//️ 异常提前 return
int* p = new int;
if (error) return; // 泄漏
️// 智能指针循环引用
shared_ptr<A> a;
shared_ptr<B> b;
a->b = b;
b->a = a; // 永不释放
2. 核心根源:堆内存的「手动管理特性」
栈和静态区无泄漏的原因是无需程序员干预,而堆内存是程序员全权负责的 :
编译器 / 操作系统不会主动回收堆内存 (即使指针失效);
只要程序员未执行释放操作,且指向堆内存的指针被销毁 / 置空 / 重赋值,就会导致泄漏。
三、内存泄漏的排查与根治方法
1.代码规范自查
- 遵循「谁分配,谁释放」原则:分配堆内存的函数,要么自己释放,要么明确告知调用者需要释放(如返回堆指针时加注释);
- new/delete、malloc/free严格匹配 :new→delete,new[]→delete[],malloc→free,禁止交叉使用;
- 指针释放后立即置空:避免野指针,也能防止重复释放(delete nullptr是安全的);
- 分支 / 循环中确保释放:if-else/for中所有分支都要写释放代码,避免遗漏;
- 异常场景下的安全释放:在catch块中释放堆内存,或使用RAII 机制;
- 容器存储堆指针时,手动遍历释放;
2.源头根治泄漏
1.核心思想:RAII 原则(资源获取即初始化)
定义:将堆资源的分配(new) 绑定到对象的构造函数,将资源的释放(delete) 绑定到对象的析构函数------ 对象创建时分配资源,对象销毁时(栈帧销毁 / 作用域结束)自动调用析构释放资源,无需程序员手动管理 。
本质:用栈的自动管理特性,管理堆的手动管理资源,从根源避免泄漏。
核心实现:C++11 智能指针(RAII 的具体实现)
智能指针是封装了堆内存指针的模板类,本质是栈上的对象,析构时自动释放指向的堆内存,支持*和->操作,用法和普通指针一致,完全替代手动new/delete。