C++_内存管理
内存管理
C++ 中对象可以存放在不同区域,不同区域的对象创建方式、销毁时机、管理责任都不同。
对象的生命周期
全局对象
定义在所有函数之外的对象。
特点:
- 在程序启动时分配
- 在程序结束时销毁
局部自动对象
定义在函数或代码块中的普通局部变量。
特点:
- 进入作用域时创建
- 离开作用域时自动销毁
例如:
1 | void f() { |
局部 static 对象
定义在函数内部,但带 static。
特点:
- 第一次执行到定义处时初始化
- 一直到程序结束时才销毁
例如:
1 | void f() { |
动态分配对象
通过 new 创建的对象。
特点:
- 存在于堆(自由空间)
- 生命周期不由作用域决定
- 必须显式释放,或交给智能指针管理
例如:
1 | int *p = new int(42); |
程序中的内存区域
复习时主要记三类:
静态内存
保存:
- 全局变量
static局部变量- 类的
static数据成员
特点:
- 程序开始前后存在
- 由编译器管理其创建和销毁
栈内存
保存:
- 函数中的普通局部变量
- 临时对象等自动对象
特点:
- 进入作用域自动分配
- 离开作用域自动释放
- 管理简单,但生命周期短
堆 / 自由空间
保存:
- 动态分配的对象
特点:
- 程序员控制分配和释放
- 更灵活
- 也更容易出错
为什么需要动态内存
动态内存适合这些场景:
- 对象大小运行时才知道
- 对象要跨作用域存在
- 需要大量对象,不适合放栈上
- 需要动态增长的数据结构
例如:
- 动态数组
- 链表、树、图
- 运行时决定大小的缓冲区
动态内存的基本操作
C++ 用两组核心操作管理动态对象:
new:分配并构造对象delete:销毁并释放对象
new
基本形式
1 | int *p = new int; |
作用:
- 在堆上分配空间
- 构造一个对象
- 返回指向该对象的指针
默认初始化
1 | int *pi = new int; // 未初始化 |
注意:
- 对内置类型,默认初始化后值未定义
- 对类类型,调用默认构造函数
直接初始化
1 | int *pi = new int(1024); |
即:创建对象时直接给初值。
列表初始化
1 | vector<int> *pv = new vector<int>{1,2,3,4,5}; |
适合类类型和容器类型。
值初始化
在类型后加空括号:
1 | int *p1 = new int; // 未初始化 |
复习时重点记:
new int:未定义值new int():值为0
分配失败
如果堆空间不足,普通 new 会抛出异常:
1 | std::bad_alloc |
例如:
1 | int *p = new int; // 失败时抛出 bad_alloc |
也可以用不抛异常的版本:
1 | int *p = new (nothrow) int; |
此时分配失败时:
- 返回
nullptr
需要头文件:
1 |
delete
基本形式
1 | delete p; |
作用:
- 销毁
p指向的对象 - 释放对应的堆内存
delete 的要求
delete 的参数必须是:
- 指向动态分配对象的指针
- 或空指针
nullptr
否则行为未定义。
错误示例
删除不是 new 得到的指针
1 | int x = 10; |
重复释放
1 | int *p = new int(42); |
空悬指针
1 | int *p = new int(42); |
删除后:
- 对象已经没了
- 但
p里可能还保留原地址
这时 p 就是空悬指针(dangling pointer)
再使用它是严重错误。
避免空悬指针
常见做法:
1 | delete p; |
这样能明确表示“现在不指向任何对象”。
动态内存常见问题
内存泄漏
申请了内存但没有释放:
1 | int *p = new int(42); |
结果:
- 这块内存直到程序结束前都回收不了
野指针 / 空悬指针
释放后还继续使用:
1 | int *p = new int(42); |
重复释放
同一地址 delete 多次,行为未定义。
为什么要用智能指针
手写 new/delete 很容易出错:
- 忘记
delete - 提前
delete - 异常导致
delete没执行 - 多处共享同一对象难管理
所以现代 C++ 推荐:
尽量不用裸
new/delete,优先使用智能指针
智能指针定义在:
1 |
智能指针概述
标准库主要有三种智能指针:
shared_ptr:共享所有权unique_ptr:独占所有权weak_ptr:弱引用,不拥有对象
共同操作
shared_ptr 和 unique_ptr 都支持:
| 操作 | 含义 |
|---|---|
p |
作为条件判断,若非空则为真 |
*p |
解引用 |
p->mem |
成员访问 |
p.get() |
返回内部原始指针 |
swap(p, q) |
交换所管理的指针 |
注意:
get()只是“拿到原始指针看看”- 不要用
get()得到的指针去delete - 也不要用它去初始化另一个智能指针
shared_ptr
shared_ptr 表示:
多个智能指针可以共同拥有同一个对象
底层有一个引用计数。
- 拷贝一个
shared_ptr:计数 +1 - 销毁一个
shared_ptr:计数 -1 - 计数变为 0:自动释放对象
创建 shared_ptr
推荐方式:
1 | auto p = make_shared<int>(42); |
优点:
- 更安全
- 更高效
- 少写一次
new
常见操作
| 操作 | 含义 |
|---|---|
make_shared<T>(args) |
创建并返回一个 shared_ptr |
shared_ptr<T> p(q) |
拷贝 q,共享对象 |
p = q |
共享 q 所指对象 |
p.use_count() |
当前共享者数量 |
p.unique() |
是否只有自己一个拥有者 |
例子
1 | auto p1 = make_shared<int>(42); |
use_count() 和 unique()
1 | p.use_count(); // 返回共享对象的 shared_ptr 数量 |
说明:
- 常用于调试或理解所有权
- 实际编程中不要过度依赖它们
不要混用裸指针和 shared_ptr
错误示例:
1 | int *p = new int(42); |
这样会导致:
- 重复释放
正确做法:
1 | auto sp1 = make_shared<int>(42); |
unique_ptr
unique_ptr 表示:
同一时刻只能有一个智能指针拥有该对象
特点:
- 不能拷贝
- 可以转移所有权
- 开销小
- 最适合“独占资源”
创建 unique_ptr
1 | unique_ptr<int> p(new int(42)); |
注意:
- 经典教材中这样写
- 现代 C++14 起也常用
make_unique,但有些教材章节可能未引入
如果可用,更推荐:
1 | auto p = make_unique<int>(42); |
不能拷贝
1 | unique_ptr<int> p1(new int(42)); |
因为所有权不能共享。
可以移动
1 | unique_ptr<int> p1(new int(42)); |
此后:
p2拥有对象p1为空
常见操作
| 操作 | 含义 |
|---|---|
u = nullptr |
释放对象并置空 |
u.release() |
放弃所有权,返回原始指针,不释放对象 |
u.reset() |
释放当前对象 |
u.reset(q) |
改为管理 q 指向对象 |
u.reset(nullptr) |
置空 |
release() 与 reset() 区别
release()
1 | int *p = u.release(); |
效果:
u不再管理对象- 返回原始指针
- 不会自动释放对象
所以必须有人接管它,否则泄漏。
reset()
1 | u.reset(); |
效果:
- 直接释放当前对象
weak_ptr
weak_ptr 是配合 shared_ptr 使用的弱引用。
特点:
- 指向
shared_ptr管理的对象 - 不增加引用计数
- 不控制对象生存期
为什么需要 weak_ptr
主要有两个用途:
- 观察某对象是否还存在
- 打破
shared_ptr循环引用
常见操作
| 操作 | 含义 |
|---|---|
weak_ptr<T> w |
空弱引用 |
weak_ptr<T> w(sp) |
观察 sp 所管理对象 |
w.reset() |
置空 |
w.use_count() |
对应对象的 shared_ptr 数量 |
w.expired() |
对象是否已释放 |
w.lock() |
若对象还在,返回一个 shared_ptr;否则返回空 shared_ptr |
基本例子
1 | auto sp = make_shared<int>(42); |
此时:
wp指向同一对象- 但不会让引用计数增加
访问 weak_ptr 所指对象
不能直接解引用 weak_ptr,应先 lock():
1 | if (auto p = wp.lock()) { |
这样才安全。
智能指针使用原则
优先使用 make_shared
1 | auto p = make_shared<string>("hello"); |
能用 unique_ptr 就尽量用 unique_ptr
因为:
- 所有权更明确
- 成本更低
- 不易误共享
不要手动 delete 智能指针管理的对象
错误:
1 | auto p = make_shared<int>(42); |
不要用同一个裸指针初始化多个智能指针
会导致重复释放。
动态数组
虽然 C++ 支持动态数组,但复习时要记住:
大多数场景优先使用
vector或string,而不是手写动态数组
因为容器更安全、更易用。
用 new 分配数组
1 | int *p = new int[10]; |
表示:
- 分配 10 个
int - 返回的是指向首元素的指针
注意:
- 得到的不是“真正的数组对象”
- 只是一个指向第一个元素的指针
因此:
- 不能直接知道长度
- 不能对它用
begin/end - 不能直接范围
for
数组初始化
默认初始化
1 | int *p = new int[10]; // 10 个未初始化 int |
值初始化
1 | int *p = new int[10](); // 10 个 0 |
列表初始化
1 | int *p = new int[10]{0,1,2,3,4,5,6,7,8,9}; |
如果初始化器不足,剩余元素值初始化。
释放动态数组
释放单个对象:
1 | delete p; |
释放数组:
1 | delete[] p; |
一定要区分!
错误示例:
1 | int *p = new int[10]; |
智能指针与动态数组
unique_ptr 管理数组
1 | unique_ptr<int[]> up(new int[10]); |
特点:
- 会自动用
delete[] - 可用下标访问:
1 | up[0] = 10; |
注意:
- 数组版
unique_ptr不支持-> - 因为它管的是数组,不是单个对象
shared_ptr 管理数组
shared_ptr 不直接内建数组版本,如果要管理数组,必须自定义删除器:
1 | shared_ptr<int> sp(new int[10], [](int *p){ delete[] p; }); |
否则默认会用 delete,出错。
访问元素时通常要借助 get():
1 | sp.get()[0] = 10; |
allocator 类
allocator 定义在:
1 |
作用:
将“分配内存”和“构造对象”分离
与 new 不同,allocator 先分配一块原始未构造内存,然后再在上面构造对象。
这主要用于:
- 标准库底层实现
- 高级内存管理
- 自定义容器
考试/复习中重点是理解思想。
为什么要分离
new 一步完成:
- 分配内存
- 构造对象
而 allocator 可以拆成两步:
allocate:只拿到原始内存construct:需要时再构造对象
这样更灵活。
常见操作
| 操作 | 含义 |
|---|---|
allocator<T> a |
可为 T 分配内存的分配器 |
a.allocate(n) |
分配能放 n 个 T 的未构造内存 |
a.deallocate(p, n) |
释放由 allocate 得到的内存 |
a.construct(p, args) |
在 p 指向位置构造对象 |
a.destroy(p) |
销毁 p 指向对象 |
使用规则
必须牢记:
allocate只分配内存,不构造对象construct后才能使用对象destroy后对象才真正销毁- 最后再
deallocate释放内存
典型流程
1 | allocator<string> alloc; |
未初始化内存算法
定义在:
1 |
这些算法用于:
- 向未构造内存中构造对象
常见算法
| 算法 | 含义 |
|---|---|
uninitialized_copy(b, e, b2) |
将 [b,e) 拷贝到未构造内存 b2 开始处 |
uninitialized_copy_n(b, n, b2) |
拷贝 n 个元素到未构造内存 |
uninitialized_fill(b, e, t) |
在未构造内存中填充对象 |
uninitialized_fill_n(b, n, t) |
在未构造内存中构造 n 个值为 t 的对象 |
注意:
- 目标内存必须足够大
- 目标必须是“未构造原始内存”
现代 C++ 的内存管理建议
复习和实际开发都推荐下面这套思路:
优先使用容器
- 动态数组优先用
vector - 字符串优先用
string
因为容器已经封装好内存管理。
需要动态对象时优先用智能指针
- 独占:
unique_ptr - 共享:
shared_ptr
尽量少直接写 new/delete
只有在:
- 实现底层结构
- 与旧代码/库交互
- 特殊资源管理
时才考虑手动管理。
易错点总结
new int 和 new int() 不一样
new int:未初始化new int():值初始化为 0
delete 和 delete[] 不一样
- 单对象:
delete p - 数组:
delete[] p
delete 后指针不会自动变成 nullptr
要手动置空:
1 | delete p; |
不要释放不是 new 得到的地址
不要重复释放同一块内存
shared_ptr 不要用同一裸指针构造多个对象
不要对智能指针 get() 出来的指针手动 delete
weak_ptr 不能直接解引用,要先 lock()
动态数组最好用 vector 替代
allocator 分配的是未构造内存,不能直接用
必须先构造对象。
小结
内存管理的主线:
- 对象可能位于:
- 静态区
- 栈
- 堆
- 堆对象通过
new创建、delete释放 - 手动管理内存容易出错:
- 泄漏
- 空悬指针
- 重复释放
- 智能指针能自动管理对象生命周期:
shared_ptr:共享unique_ptr:独占weak_ptr:弱引用
- 动态数组通常不如
vector安全方便 allocator用于“分配内存”和“构造对象”分离
注
现代 C++:优先用容器,次选智能指针,最后才是手写
new/delete。




