欢迎光临
我们一直在努力

【C++】C++动态内存管理:new/delete完全指南

📌 相关专栏

  • 【Linux专栏】
  • 【C语言专栏】
  • 【测试专栏】
  • 【MySQL专栏】
  • 【C++ 专栏】

📌 相关文章推荐

  • 【C+++】C++类和对象5:一文搞懂C++类型转换、初始化列表与static成员
  • 【C++】C++类与对象4: 一文吃透C++友元、内部类、匿名对象与编译优化要点
  • 【C++】C++核心语法:函数重载与缺省参数原理与避坑
  • 【C语言】超实用VS调试干货,新手也能轻松排错

举爪爪打招呼

很高兴你点开这篇文章✨

这里会持续更新我喜欢的内容,关注我,一起慢慢变好呀

👍 点赞 ⭐ 收藏 💬 评论


文章目录

  • 前言
  • 一、new/delete的基本用法
    • 1.1 单个对象的分配与释放
    • 1.2 带初始化的分配
    • 1.3 数组的分配与释放
  • 二、new/delete vs malloc/free
    • 2.1 核心区别
    • 2.2 最重要的区别:构造/析构
    • 2.3 不要混用
  • 三、new和delete的底层原理
    • 3.1 new的工作流程
    • 3.2 delete的工作流程
  • 四、自定义类型的数组分配
    • 4.1 new[ ]和delete[ ]必须匹配
    • 4.2 数组初始化方式
  • 五、内存分配失败处理
    • 5.1 new的失败处理机制
    • 5.2 循环分配直到失败
    • 5.3 不抛异常的new(nothrow)
  • 六、定位new表达式(placement new)
  • 七、常见错误与最佳实践
    • 7.1 new和delete不匹配
    • 7.2 忘记释放内存(内存泄漏)
    • 7.3 重复释放(double free)
    • 7.4 释放后继续使用(野指针)
  • 八、链表节点示例
  • 九、知识点汇总
  • 十、常见面试题

前言

在C语言中,我们用malloc和free来管理动态内存。到了C++,虽然兼容C的方式,但引入了更强大的new和delete操作符。

🐾 这一篇我们来学习:

  • new和delete的基本用法
  • 为什么C++要引入新的内存管理方式
  • new[]和delete[]的正确匹配
  • 内存分配失败怎么办
  • 常见错误和最佳实践

🐶 🐾 ✨ 🐾 🐶


一、new/delete的基本用法

1.1 单个对象的分配与释放

// C语言方式
int* p1 = (int*)malloc(sizeof(int));
free(p1);

// C++方式
int* p2 = new int; // 分配一个int大小的内存
delete p2; // 释放内存


1.2 带初始化的分配

// 分配并初始化
int* p3 = new int(0); // 初始化为0
int* p4 = new int(10); // 初始化为10
int* p5 = new int{20}; // C++11统一初始化

delete p3;
delete p4;
delete p5;


1.3 数组的分配与释放

// 分配数组
int* arr1 = new int[10]; // 10个未初始化的int
int* arr2 = new int[10]{0}; // 10个初始化为0的int
int* arr3 = new int[10]{1,2,3,4,5}; // 前5个指定值,后5个初始化为0

// 释放数组(注意:必须用delete[])
delete[] arr1;
delete[] arr2;
delete[] arr3;

🐶 🐾 ✨ 🐾 🐶


二、new/delete vs malloc/free

2.1 核心区别

对比项malloc/freenew/delete
语言 C语言函数 C++操作符
类型安全 需要强转(void*) 类型安全,无需强转
内存大小 手动计算sizeof 编译器自动计算
构造函数 不调用 自动调用
析构函数 不调用 自动调用
失败返回 NULL 抛出bad_alloc异常

2.2 最重要的区别:构造/析构

class A
{
public:
A(int a1 = 0, int a2 = 0) : _a1(a1), _a2(a2)
{
cout << "A(int a1 = 0, int a2 = 0)" << endl;
}
~A()
{
cout << "~A()" << endl;
}
private:
int _a1 = 1;
int _a2 = 1;
};

int main()
{
// malloc只会分配内存,不会调用构造函数
A* p1 = (A*)malloc(sizeof(A)); // 无输出

// new会分配内存并调用构造函数
A* p2 = new A; // 输出:A(int a1 = 0, int a2 = 0)
A* p3 = new A(1, 2); // 输出:A(int a1 = 0, int a2 = 0)

free(p1); // 不会调用析构函数
delete p2; // 调用析构函数后释放内存
delete p3;

return 0;
}


2.3 不要混用

绝对禁止混用malloc/free和new/delete:

int main()
{
int* p1 = new int;
free(p1); // ❌ 未定义行为

int* p2 = (int*)malloc(sizeof(int));
delete p2; // ❌ 未定义行为

A* p3 = new A;
free(p3); // ❌ 不会调用析构函数,可能导致资源泄漏

A* p4 = (A*)malloc(sizeof(A));
delete p4; // ❌ 未定义行为

return 0;
}

🐶 🐾 ✨ 🐾 🐶


三、new和delete的底层原理

3.1 new的工作流程

A* p = new A(1, 2);

等价于:

1. 调用operator new函数分配原始内存(底层调用malloc) 2. 在分配的内存上调用A的构造函数 3. 返回指向对象的指针


3.2 delete的工作流程

delete p;

等价于:

1. 调用p指向对象的析构函数 2. 调用operator delete函数释放内存(底层调用free)


四、自定义类型的数组分配

4.1 new[ ]和delete[ ]必须匹配

int main()
{
// 正确匹配
A* p1 = new A[5];
delete[] p1; // ✅ 调用5次析构函数

// 错误匹配
A* p2 = new A[5];
delete p2; // ❌ 只调用1次析构函数,可能导致内存泄漏

B* p3 = new B[10];
delete p3; // ❌ 可能崩溃或未定义行为

return 0;
}

🐶 🐾 ✨ 🐾 🐶


4.2 数组初始化方式

int main()
{
// 方式1:先用有名对象初始化
A aa1(1, 1);
A aa2(2, 2);
A aa3(3, 3);
A* p1 = new A[3]{aa1, aa2, aa3};

// 方式2:用匿名对象初始化
A* p2 = new A[3]{ A(1,1), A(2,2), A(3,3) };

// 方式3:C++11统一初始化
A* p3 = new A[3]{ {1,1}, {2,2}, {3,3} };

delete[] p1;
delete[] p2;
delete[] p3;

return 0;
}

🐶 🐾 ✨ 🐾 🐶


五、内存分配失败处理

5.1 new的失败处理机制

  • C语言中malloc失败返回NULL,需要手动判断:

int* p = (int*)malloc(1024 * 1024 * 1024);
if (p == NULL) {
// 处理内存不足
}


  • C++的new失败时抛出异常,而不是返回NULL:

int main()
{
try
{
// 尝试分配1GB内存
void* p1 = new char[1024 * 1024 * 1024];
cout << p1 << endl;

void* p2 = new char[1024 * 1024 * 1024];
cout << p2 << endl;

void* p3 = new char[1024 * 1024 * 1024];
cout << p3 << endl;
}
catch (const exception& e)
{
cout << e.what() << endl; // 输出:bad allocation
}

return 0;
}


5.2 循环分配直到失败

void func()
{
int n = 1;
while (1)
{
void* p1 = new char[1024 * 1024]; // 每次分配1MB
cout << p1 << "->" << n << endl;
++n;
}
}

int main()
{
try
{
func();
}
catch (const exception& e)
{
cout << e.what() << endl; // 最终会抛出bad_alloc
}
return 0;
}


5.3 不抛异常的new(nothrow)

如果不想用异常,可以使用nothrow版本:

#include <new> // 需要包含这个头文件

int main()
{
int* p = new (nothrow) int[1024 * 1024 * 1024];
if (p == nullptr)
{
cout << "内存分配失败" << endl;
}
delete[] p;
return 0;
}

🐶 🐾 ✨ 🐾 🐶


六、定位new表达式(placement new)

定位new可以在已分配的内存上构造对象:

#include <new> // 需要包含这个头文件

int main()
{
// 分配原始内存
void* p = malloc(sizeof(A));

// 在已分配的内存上构造A对象
A* a = new(p) A(1, 2);

// 使用对象
a->Print();

// 手动调用析构函数
a->~A();

// 释放原始内存
free(p);

return 0;
}

🐾 使用场景: 内存池、自定义内存管理、嵌入式系统。

🐶 🐾 ✨ 🐾 🐶


七、常见错误与最佳实践

7.1 new和delete不匹配

分配方式释放方式
new delete
new[ ] delete[ ]
malloc free

// ❌ 错误示例
A* p1 = new A;
delete[] p1; // 错误!

A* p2 = new A[10];
delete p2; // 错误!只调用一次析构

// ✅ 正确示例
A* p1 = new A;
delete p1;

A* p2 = new A[10];
delete[] p2;


7.2 忘记释放内存(内存泄漏)

// ❌ 内存泄漏
void func()
{
int* p = new int[100];
// 忘记delete[] p
}

// ✅ 正确做法
void func()
{
int* p = new int[100];
// … 使用p
delete[] p;
}


7.3 重复释放(double free)

int* p = new int;
delete p;
delete p; // ❌ 未定义行为,可能崩溃


7.4 释放后继续使用(野指针)

int* p = new int(10);
delete p;
*p = 20; // ❌ 未定义行为,p已经是野指针

// ✅ 释放后置空
delete p;
p = nullptr;

🐶 🐾 ✨ 🐾 🐶


八、链表节点示例

struct ListNode
{
int val;
ListNode* next;

ListNode(int x)
: val(x)
, next(nullptr)
{}
};

int main()
{
// 创建链表:1->2->3->4
ListNode* n1 = new ListNode(1);
ListNode* n2 = new ListNode(2);
ListNode* n3 = new ListNode(3);
ListNode* n4 = new ListNode(4);

n1->next = n2;
n2->next = n3;
n3->next = n4;

// 遍历链表
ListNode* cur = n1;
while (cur)
{
cout << cur->val << " ";
cur = cur->next;
}
cout << endl;

// 释放链表内存
cur = n1;
while (cur)
{
ListNode* next = cur->next;
delete cur;
cur = next;
}

return 0;
}

🐶 🐾 ✨ 🐾 🐶


九、知识点汇总

知识点核心要点
new 分配内存 + 调用构造函数
delete 调用析构函数 + 释放内存
new[ ] 分配数组内存 + 依次调用构造
delete[ ] 依次调用析构 + 释放数组内存
malloc/free 只分配/释放内存,不调用构造/析构
匹配规则 new配delete,new[]配delete[]
失败处理 抛bad_alloc异常(可用nothrow)
定位new 在已分配内存上构造对象

🐶 🐾 ✨ 🐾 🐶


十、常见面试题

🐾 Q1:new和malloc的区别?

  • 1. new是操作符,malloc是函数
  • 2. new会调用构造函数,malloc不会
  • 3. new返回类型安全的指针,malloc返回void*
  • 4. new失败抛异常,malloc失败返回NULL
  • 5. new不需要手动计算大小,malloc需要sizeof

🐾 Q2:delete和free的区别?

  • 1. delete会调用析构函数,free不会
  • 2. delete是操作符,free是函数

🐾 Q3:new[]和delete[]不匹配会怎样?

  • 未定义行为。可能内存泄漏、程序崩溃、只调用一次析构函数。

🐾 Q4:如何避免内存泄漏?

  • 1. 确保new和delete成对出现
  • 2. 使用智能指针(后续课程)
  • 3. 遵循RAII原则

下一篇我们来学习:

  • 模板原理
  • 实例化
  • 特化

🐶 🐾 ✨ 🐾 🐶


举爪爪求关注

谢谢你看到这里呀

如果喜欢这篇内容,点个关注,下次更新不迷路✨

👍 点赞 ⭐ 收藏 💬 评论

赞(0)
未经允许不得转载:171主机测评 » 【C++】C++动态内存管理:new/delete完全指南
分享到: 更多 (0)

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址