一、堆区是什么?
堆区是 C++ 程序运行时,由程序员手动申请、手动释放的一块动态内存区域 。它不属于任何函数,不属于任何局部作用域,生命周期完全由程序员自己控制。
- 栈:系统自动管
- 堆:程序员自己管
- 全局区:程序启动就有,结束才没
- 堆:想用就
new,不用就delete
二、堆区的核心特点
1. 内存分配方式:动态分配
程序运行过程中随时可以申请、随时可以释放,大小可以任意指定。
2. 内存不连续
堆是链表结构 管理空闲块,多次申请的内存地址不连续。
3. 空间巨大
栈只有几 MB,堆可以用到 几 GB(受操作系统限制)。
4. 分配速度比栈慢
因为需要遍历空闲链表、分配、标记、防止碎片。
5. 生命周期由程序员控制
new→ 分配delete→ 释放- 不释放 → 内存泄漏
- 释放后再用 → 野指针、崩溃
6. 多线程共享
同一个进程内的所有线程,共享同一个堆空间。
三、堆区用来存什么?
- 动态大小的数组(运行时才知道大小)
- 大对象(太大栈放不下)
- 需要跨函数、跨作用域使用的对象
- 容器底层(vector、string、map 全在堆上)
四、堆区的使用方式(C++ 标准)
1. 申请单个变量
cpp
运行
int* p = new int(100); // 堆上分配 int,值=100
2. 申请数组
cpp
运行
int* arr = new int[10]; // 堆上分配 10 个 int 的数组
3. 释放内存
cpp
运行
delete p; // 释放单个变量
delete[] arr; // 释放数组 必须加[]
4. 最规范写法
cpp
运行
int* p = new int(20);
delete p;
p = nullptr; // 释放后置空,避免野指针
五、堆区底层原理(非常重要)
1. 堆内存如何分配?
操作系统维护一个 空闲内存链表
new→ 遍历链表找一块足够大的内存- 标记为已使用
- 返回地址给程序
2. 堆内存增长方向
从低地址 → 高地址(和栈相反)
3. 堆内存不会自动释放
只有两种情况会释放:
- 程序员
delete - 程序结束,OS 回收
六、堆区常见错误(90% 的人都踩过)
1. 内存泄漏(最常见)
cpp
运行
void func() {
int* p = new int(10);
} // 函数结束,p 销毁,但堆内存还在!泄漏了
2. 重复释放
cpp
运行
int* p = new int;
delete p;
delete p; // 崩溃!重复释放堆内存
3. 释放后继续使用(野指针)
cpp
运行
int* p = new int(10);
delete p;
*p = 20; // 严重错误!访问已释放的堆内存
4. new /delete 不匹配
cpp
运行
int* arr = new int[10];
delete arr; // 错误!应该用 delete[]
5. 不初始化就使用
cpp
运行
int* p = new int; // 未初始化,值是随机垃圾数
七、堆 vs 栈(最清晰对比)
表格
| 特点 | 栈区(stack) | 堆区(heap) |
|---|---|---|
| 谁管理 | 系统自动分配、自动释放 | 程序员手动 new/delete |
| 速度 | 极快 | 较慢 |
| 空间 | 小(MB 级别) | 大(GB 级别) |
| 连续性 | 连续内存 | 不连续 |
| 生命周期 | 函数结束即销毁 | 手动控制 |
| 碎片 | 无碎片 | 容易产生内存碎片 |
| 方向 | 高地址 → 低地址 | 低地址 → 高地址 |
| 报错 | 栈溢出 | 内存不足、野指针、重复释放 |
八、堆区使用规范(企业级标准)
- 谁申请,谁释放
new必须配对delete,new[]配对delete[]- 释放后指针立刻置空
nullptr - 不要返回栈指针,但可以返回堆指针
- 能用栈就不用堆,堆更耗性能
- 现代 C++ 尽量用 智能指针(unique_ptr/shared_ptr) 自动管理堆内存,避免泄漏