C++内存管理:从内存分布到new/delete底层原理
一、C/C++内存分布
在开始讨论动态内存管理之前,我们先来搞清楚一个问题:程序中的各种变量分别存储在内存的哪个区域?
先看下面这段代码:
int globalvar = 1; static int staticGlobalvar = 1; void Test() { static int staticvar = 1; int localvar = 1; int num1[10] = {1,2,3,4}; char char2[] = "abcd"; const char* pChar3 = "abcd"; int* ptr1 = (int*)malloc(sizeof(int) * 4); int* ptr2 = (int*)calloc(4, sizeof(int)); int* ptr3 = (int*)realloc(ptr2, sizeof(int) * 4); free(ptr1); free(ptr3); }你能准确说出每个变量存储在哪个区域吗?
globalvar→ 数据段(静态区)staticGlobalvar→ 数据段(静态区)staticvar→ 数据段(静态区)localvar→ 栈num1→ 栈char2→ 栈*char2→ 栈(因为char2是数组,内容在栈上)pChar3→ 栈*pChar3→ 代码段(常量区),因为指向的是字符串常量ptr1→ 栈*ptr1→ 堆
内存区域划分
| 区域 | 存储内容 | 特点 |
|---|---|---|
| 栈 | 非静态局部变量、函数参数、返回值等 | 向下增长,自动管理 |
| 堆 | 动态分配的内存 | 向上增长,需手动管理 |
| 数据段(静态区) | 全局变量、静态变量 | 程序运行时分配,结束时释放 |
| 代码段(常量区) | 可执行代码、只读常量 | 只读,不可修改 |
二、C语言的动态内存管理
在C语言中,我们使用malloc、calloc、realloc和free来管理动态内存。
malloc/calloc/realloc 的区别
| 函数 | 参数 | 初始化 | 返回值 |
|---|---|---|---|
malloc | 字节数 | 不初始化 | void* |
calloc | 个数,每个大小 | 初始化为0 | void* |
realloc | 原指针,新大小 | 不初始化 | void* |
// 使用示例 int* p2 = (int*)malloc(4 * sizeof(int)); // 申请4个int int* p3 = (int*)calloc(4, sizeof(int)); // 申请4个int并初始化为0 int* p4 = (int*)realloc(p2, sizeof(int)*10); // 扩容到10个int // 注意:如果realloc扩容成功,p2会被自动释放,不需要再free(p2) free(p3); free(p4);三、C++的内存管理方式
C++除了兼容C的内存管理方式外,还引入了new和delete操作符。
3.1 内置类型的使用
// 申请和释放单个对象 int* ptr1 = new int; // 申请一个int int* ptr2 = new int(10); // 申请一个int并初始化为10 delete ptr1; delete ptr2; // 申请和释放数组 int* ptr3 = new int[10]; // 申请10个int delete[] ptr3; // 注意:必须使用delete[]3.2 自定义类型的使用
new和delete与malloc和free最大的区别:new/delete会调用构造函数/析构函数!
class A { public: A(int a = 0) : _a(a) { cout << "A():" << this << endl; } ~A() { cout << "~A():" << this << endl; } private: int _a; }; int main() { // malloc只会开辟空间,不会调用构造函数 A* p1 = (A*)malloc(sizeof(A)); // new会开辟空间 + 调用构造函数 A* p2 = new A(10); // free只会释放空间,不会调用析构函数 free(p1); // delete会调用析构函数 + 释放空间 delete p2; return 0; }四、operator new 与 operator delete
很多人不知道的是,new和delete实际上是操作符,而operator new和operator delete是全局函数。
底层实现原理
// operator new 的简化实现 void* __CRTDECL operator new(size_t size) { void* p; while ((p = malloc(size)) == 0) { if (_callnewh(size) == 0) { // 申请失败,抛出 bad_alloc 异常 throw std::bad_alloc(); } } return p; } // operator delete 的简化实现 void operator delete(void* pUserData) { if (pUserData == NULL) return; free(pUserData); }关键点:
operator new底层调用malloc,申请失败时抛异常而不是返回NULLoperator delete底层调用free
五、new 和 delete 的实现原理
5.1 内置类型
对于内置类型,new和malloc、delete和free基本类似,区别在于:
new失败时抛异常,malloc失败返回NULLnew[]/delete[]用于连续空间
5.2 自定义类型
new的原理:
调用
operator new申请空间在申请的空间上执行构造函数
delete的原理:
在空间上执行析构函数
调用
operator delete释放空间
new T[N]的原理:
调用
operator new[](实际调用operator new)申请N个对象空间执行N次构造函数
delete[]的原理:
执行N次析构函数
调用
operator delete[](实际调用operator delete)释放空间
六、定位new表达式(placement new)
定位new允许在已分配的内存上调用构造函数初始化对象。
// 使用场景:内存池分配的内存需要初始化 A* p1 = (A*)malloc(sizeof(A)); // 只分配内存,未构造对象 new(p1) A(10); // 在p1指向的内存上构造对象 p1->~A(); // 显式调用析构函数 free(p1);适用场景:配合内存池使用,在已分配的内存上创建对象。
七、malloc/free 与 new/delete 的区别
| 对比项 | malloc/free | new/delete |
|---|---|---|
| 本质 | 函数 | 操作符 |
| 初始化 | 不会初始化 | 可以初始化 |
| 大小计算 | 需手动计算 | 编译器自动计算 |
| 返回值 | void*,需强转 | 类型安全,无需强转 |
| 失败处理 | 返回NULL,需判空 | 抛异常,需捕获 |
| 自定义类型 | 不调用构造/析构 | 调用构造/析构 |
八、常见问题与最佳实践
8.1 内存泄漏
内存泄漏是指动态分配的内存没有被正确释放,导致内存浪费。
常见场景:
忘记
delete/free异常导致
delete未执行使用
new[]却用delete释放
8.2 最佳实践
匹配使用:
new配delete,new[]配delete[]RAII原则:使用智能指针(
unique_ptr、shared_ptr)管理资源避免裸指针:尽可能使用智能指针
定期检查:使用内存检测工具定期检查内存泄漏
总结
C++的内存管理非常灵活但也很容易出错。记住以下几点:
理解内存分布(栈、堆、数据段、代码段)
掌握
new/delete与malloc/free的区别理解
operator new/operator delete的底层原理遵循最佳实践,使用智能指针管理内存