C和C++的内存管理 续篇
上文提到了内存管理的基本方法,本文则从更底层的层面来学习new/delete的有关知识。
operator new与operator delete函数
与malloc/free不同的是,new和delete不是函数,new和delete是用户进行动态内存申请和释放的操作符,operator new 和operator delete是 系统提供的全局函数,new在底层调用operator new全局函数来申请空间,delete在底层通过 operator delete全局函数来释放空间。
operator new 和operator delete的实现
void * __CRTDECL operator new ( size_t size ) _THROW1 ( _STD bad_alloc ){// try to allocate size bytes// 该函数实际通过 malloc 来申请空间void * p ;while (( p = malloc ( size )) == 0 )if ( _callnewh ( size ) == 0 ){// 如果申请内存失败了,这里会抛出 bad_alloc 类型异常static const std::bad_alloc nomem ;_RAISE ( nomem );}return ( p );}/*operator delete: 该函数最终是通过 free 来释放空间的*/void operator delete ( void * pUserData ){_CrtMemBlockHeader * pHead ;RTCCALLBACK ( _RTC_Free_hook , ( pUserData , 0 ));if ( pUserData == NULL )return ;_mlock ( _HEAP_LOCK ); /* block other threads */__TRY/* get a pointer to memory block header */pHead = pHdr ( pUserData );/* verify block type */_ASSERTE ( _BLOCK_TYPE_IS_VALID ( pHead -> nBlockUse ));_free_dbg ( pUserData , pHead -> nBlockUse );__FINALLY_munlock ( _HEAP_LOCK ); /* release other threads */__END_TRY_FINALLYreturn ;}
new和delete的实现原理
内置类型
-
new:使用
operator new
分配内存(类似malloc
),失败时抛出bad_alloc
异常,处理单个元素。 -
delete:使用
operator delete
释放内存(类似free
)。 -
new[]
/delete[]
:
处理连续空间,new[]
分配后不调用构造函数(内置类型无构造),delete[]
直接释放。
自定义类型
-
new:
-
调用
operator new
分配内存。 -
在分配的内存上调用对象的构造函数。
-
-
delete:
-
调用对象的析构函数,清理资源。
-
调用
operator delete
释放内存。
-
-
new T[N]/delete[]:
-
new[]
:调用operator new[]
分配连续内存(内部通过operator new
完成),对每个元素调用构造函数。 -
delete[]
:对每个元素调用析构函数,再调用operator delete[]
释放内存(内部通过operator delete
实现)。
-
定位new表达式
new (place_address) type 或者 new (place_address) type(initializer-list)place_address 必须是一个指针, initializer-list 是类型的初始化列表
C/C++中内存管理方式的主要区别
1.malloc/free是函数,new/delete是操作符。
2.malloc只能开辟空间,new还可以直接初始化对象,malloc开辟失败返回null,而new会抛异常
3.malloc返回值为void*类型,使用时根据实际元素类型要类型转换,而new后直接跟类型即可。
4.malloc/free
需严格手动管理释放,易遗漏导致内存泄漏。new/delete
可通过智能指针(如 std::unique_ptr
、std::shared_ptr
)自动管理生命周期,降低泄漏风险。
5.new/delete
是 C++ 特性,强制与构造函数/析构函数绑定,更适合面向对象编程。
核心原则:
C++ 中优先使用
new/delete
管理动态内存,确保对象生命周期安全。避免混用
malloc/free
和new/delete
,严格配对使用。利用智能指针和 RAII 机制,减少手动内存管理错误。
内存泄漏
分类
堆内存泄漏(Heap leak)
系统资源泄漏
常见场景
1.动态内存未释放
使用
new
/malloc
分配内存后,忘记调用delete
/free
void func() {
int* ptr = new int[100]; // 未释放
// ...
}
2.异常导致资源未释放
在
new
和delete
之间发生异常,导致释放代码未执行void loadData() {
int* data = new int[1024];
processData(); // 若此处抛出异常,data 未释放!
delete[] data;
}
3.容器中的指针未清理
容器(如
vector<Object*>
)存储指针,容器销毁时未手动释放指针指向的内存std::vector<MyClass*> vec;
vec.push_back(new MyClass()); // 容器销毁时未 delete
4.循环引用(智能指针)
使用
std::shared_ptr
时,对象互相引用,导致引用计数无法归零class A {
std::shared_ptr<B> b_ptr;
};
class B {
std::shared_ptr<A> a_ptr; // A 和 B 互相持有 shared_ptr,导致内存泄漏
};
5.第三方资源未释放
文件句柄、数据库连接、网络资源等未关闭
FILE* file = fopen("data.txt", "r");
// ... 未调用 fclose(file)
注意事项
那么针对这些问题,我们需要注意以下几点
优先使用智能指针
方法:
使用
std::unique_ptr
或std::shared_ptr
管理动态内存。对于循环引用,用
std::weak_ptr
打破强引用。示例:
std::unique_ptr<int> ptr = std::make_unique<int>(42); // 自动释放遵循 RAII 原则
方法:将资源(内存、文件句柄等)封装在对象中,利用析构函数自动释放。
示例:
class FileHandler { public: FileHandler(const char* path) { file_ = fopen(path, "r"); } ~FileHandler() { if (file_) fclose(file_); } private: FILE* file_; };确保异常安全
方法:
在可能抛出异常的代码中使用智能指针。
使用
try-catch
确保异常后资源释放。示例:
try { auto ptr = std::make_unique<Resource>(); riskyOperation(); // 可能抛出异常 } catch (...) { // 异常时 ptr 会自动释放 }避免手动管理内存
方法:
使用标准库容器(如
vector
、string
)替代裸指针数组。使用
std::string
而非char*
。示例:
std::vector<int> data(100); // 无需手动释放工具检测与调试
方法:
使用 Valgrind(Linux)或 Dr. Memory(Windows)检测内存泄漏。
开启编译器工具(如 GCC/Clang 的
-fsanitize=address
)。示例:
valgrind --leak-check=full ./your_program
到这里,关于C/C++中的动态内存管理的知识就告一段落了,这些知识非常重要,是C++中值得关注的问题,尤其是关于内存泄露的避免,C++学习者们应该要注意到的。