内存管理(C++)
内存管理
- 1. 内存管理概述
- 2. 栈(Stack)内存
- 3. 堆(Heap)内存
- 4. new / delete 操作符
- 5. 内存泄漏基础及成因
- 6. 避免内存泄漏的最佳实践
- RAII(Resource Acquisition Is Initialization)
- 智能指针(C++11 及以后)
- 异常安全
- 工具检测
- 小结
1. 内存管理概述
-
为什么要管理内存?
C++ 是一门允许程序员直接控制内存分配与释放的语言。合理的内存管理可以:-
提高性能(减少系统调用、避免频繁分配/释放)
-
降低内存占用(释放不再使用的对象)
-
防止程序崩溃(访问无效内存)
-
-
手动 vs 自动
-
自动管理:局部变量、函数参数等,由编译器在作用域结束时自动释放。
-
手动管理:通过 new/delete、malloc/free(C 风格)等方式,在堆上分配和释放内存,需要程序员自己管理。
-
2. 栈(Stack)内存
-
分配与释放
- 在函数内部声明的局部变量,编译器会在函数调用时为其在栈上分配空间,函数返回时自动释放。
属性 | 描述 |
---|---|
速度 | 非常快,分配/释放只需调整栈指针 |
生命周期 | 与作用域一致,作用域结束即释放 |
大小限制 | 受操作系统或编译器栈大小限制(几 MB) |
访问方式 | LIFO(后进先出) |
- 示例
void foo() {int a = 42; // 在栈上分配 sizeof(int)double arr[100]; // 在栈上分配 100 * sizeof(double)
} // 离开作用域时,a 和 arr 自动销毁
3. 堆(Heap)内存
-
分配与释放
- 通过 new(或 malloc)在运行时动态分配,必须显式调用 delete(或 free)释放。
属性 | 描述 |
---|---|
速度 | 相对慢,需要操作系统/运行时参与 |
生命周期 | 直到显式释放(或程序结束) |
大小限制 | 受限于系统可用内存,通常远大于栈 |
管理方式 | 任意顺序分配/释放,容易产生碎片 |
- 示例
void bar() {int* p = new int(5); // 在堆上分配一个 int 并初始化为 5MyClass* obj = new MyClass; // 在堆上分配一个对象,调用其默认构造函数// … 使用 p、obj …delete p; // 释放堆内存,调用对应析构delete obj; // 释放对象内存
}
4. new / delete 操作符
-
new
-
分配内存并调用构造函数
-
返回指向所分配类型的指针,如果分配失败,会抛出 std::bad_alloc(可以改用 new(nothrow) 返回 nullptr)
-
-
delete
-
调用析构函数并释放内存
-
对应数组要用 delete[]
-
-
示例
// 单个对象
int* pi = new int(10);
delete pi;// 对象数组
double* pd = new double[50];
delete[] pd;
-
注意事项
-
成对使用:new ↔ delete,new[] ↔ delete[]
-
不要对同一块内存重复 delete(双重释放)
-
不要释放非堆内存(如栈内存、静态/全局区)
-
释放后指针要置 nullptr,防止悬空指针
-
5. 内存泄漏基础及成因
-
什么是内存泄漏?
- 程序在堆上分配了内存但没有对应的释放,使得这部分内存无法再被程序利用,长时间运行可能耗尽可用内存。
-
常见成因
- 遗漏 delete
void f() {int* p = new int[100];// …忘记 delete[] p… } // 离开作用域,p 指针销毁,但堆内存仍然保留
- 异常导致提前返回
void g() {MyClass* obj = new MyClass;if (something_wrong()) return; // 忘记 delete objdelete obj; }
-
多重所有权、混乱释放时机
多个指针指向同一块内存,但无法确定何时释放;最终没人负责调用 delete。 -
循环引用(智能指针场景)
std::shared_ptr 循环引用时,引用计数永不归零。
6. 避免内存泄漏的最佳实践
RAII(Resource Acquisition Is Initialization)
- 将资源(包括内存)封装在对象生命周期内,让构造函数申请、析构函数释放。
class Buffer {
public:Buffer(size_t n): data_(new char[n]) {}~Buffer() { delete[] data_; }
private:char* data_;
};void h() {Buffer buf(1024); // 离开作用域自动释放
}
智能指针(C++11 及以后)
-
std::unique_ptr:独占所有权,自动释放
-
std::shared_ptr:共享所有权,引用计数归零时释放
-
std::weak_ptr:配合 shared_ptr 打破循环引用
#include <memory>void k() {auto up = std::make_unique<MyClass>(args…);auto sp = std::make_shared<MyClass>(args…);
} // up、sp 离开作用域自动 delete
异常安全
- 在有可能抛出异常的上下文中,尽量使用智能指针或容器而非裸指针。
工具检测
-
Valgrind(Linux)
-
AddressSanitizer(编译器选项 -fsanitize=address)
-
定期进行内存检查,捕获早期泄漏。
小结
-
栈:分配/释放迅速,生命周期受作用域控制,但容量有限。
-
堆:容量大,可动态管理,但需显式 new/delete,且分配释放较慢。
-
new/delete:必须成对使用,注意异常安全与数组版本。
-
内存泄漏:由遗漏释放、异常分支、循环引用等导致,会积累“僵尸”内存。
-
最佳实践:遵循 RAII、优先使用智能指针、借助检测工具,保证代码健壮、可靠。