内存管理(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、优先使用智能指针、借助检测工具,保证代码健壮、可靠。
 
