目录
[二、new/delete 与 malloc/free 的对比](#二、new/delete 与 malloc/free 的对比)
[三、new 和 delete 的工作原理](#三、new 和 delete 的工作原理)
[new 做了两件事](#new 做了两件事)
[delete 做了两件事](#delete 做了两件事)
[四、new[] 和 delete[]:数组的配对](#四、new[] 和 delete[]:数组的配对)
[六、placement new:在已分配内存上构造对象](#六、placement new:在已分配内存上构造对象)
[错误1:混用 malloc/free 和 new/delete](#错误1:混用 malloc/free 和 new/delete)
[错误2:忘记 delete 导致内存泄漏](#错误2:忘记 delete 导致内存泄漏)
[错误3:重复 delete](#错误3:重复 delete)
[错误4:delete 后继续使用指针](#错误4:delete 后继续使用指针)
一、一个会崩溃的程序
先看这段代码,猜猜会发生什么:
cpp
#include <iostream>
#include <cstdlib>
#include <string>
using namespace std;
class Student {
private:
string name;
int age;
public:
Student(const string& n, int a) : name(n), age(a) {
cout << "构造函数: " << name << endl;
}
~Student() {
cout << "析构函数: " << name << endl;
}
void speak() {
cout << "我是" << name << ", " << age << "岁" << endl;
}
};
int main() {
// 错误示范1:malloc 分配对象,不调用构造函数
Student* s1 = (Student*)malloc(sizeof(Student));
s1->speak(); // 未定义行为!name 和 age 未初始化
// 错误示范2:new 分配的对象用 free 释放
Student* s2 = new Student("张三", 20);
free(s2); // 未定义行为!没有调用析构函数
return 0;
}
运行结果:可能输出乱码、崩溃,或者看起来"正常"但内存泄漏。
问题:
-
malloc只分配原始内存,不调用构造函数 → 对象处于未初始化状态 -
free只释放内存,不调用析构函数 →string的内部资源泄漏
二、new/delete 与 malloc/free 的对比
| 特性 | malloc / free |
new / delete |
|---|---|---|
| 头文件 | <cstdlib> |
不需要(C++ 关键字) |
| 返回值 | void*(需要强转) |
类型化指针(无需强转) |
| 内存大小 | 手动计算 sizeof |
编译器自动计算 |
| 构造函数 | ❌ 不调用 | ✅ 调用 |
| 析构函数 | ❌ 不调用 | ✅ 调用 |
| 失败时 | 返回 NULL |
抛出 bad_alloc 异常 |
| 配对要求 | 必须 malloc/free 配对 |
必须 new/delete 配对 |
正确用法示例
cpp
// ✅ 正确:new/delete 配对
Student* s1 = new Student("李四", 21);
delete s1;
// ✅ 正确:malloc/free 配对(用于 POD 类型尚可,但不推荐)
int* arr = (int*)malloc(10 * sizeof(int));
free(arr);
// ❌ 错误:混用
Student* s2 = (Student*)malloc(sizeof(Student));
delete s2; // 未定义行为
// ❌ 错误:混用
Student* s3 = new Student("王五", 22);
free(s3); // 未定义行为
三、new 和 delete 的工作原理
new 做了两件事
-
分配内存 (类似
malloc) -
调用构造函数(在分配的内存上构造对象)
cpp
Student* s = new Student("赵六", 23);
// 编译器大致转换成:
// 1. void* mem = operator new(sizeof(Student)); // 分配内存
// 2. Student* s = new(mem) Student("赵六", 23); // placement new 调用构造
delete 做了两件事
-
调用析构函数
-
释放内存 (类似
free)
cpp
delete s;
// 编译器大致转换成:
// 1. s->~Student(); // 调用析构
// 2. operator delete(s); // 释放内存
四、new[] 和 delete[]:数组的配对
为数组分配内存时,必须使用 new[] 和 delete[] 配对。
cpp
// ✅ 正确:new[] / delete[] 配对
Student* arr = new Student[3] { {"A", 1}, {"B", 2}, {"C", 3} };
delete[] arr; // 调用 3 次析构函数
// ❌ 错误:new[] 配 delete(只调用一次析构)
Student* arr2 = new Student[3];
delete arr2; // 未定义行为!只析构第一个元素,且释放内存错误
为什么不能混用?
new[] 分配时会在内存块开头(通常是前 4-8 字节)存储数组元素个数,以便 delete[] 知道要调用多少次析构函数。
cpp
// new[] 分配的内存布局(示意)
// [元素个数][元素0][元素1][元素2]...
// 4字节 N字节 N字节 N字节
如果用 delete(而不是 delete[]):
-
编译器以为只有一个对象
-
只调用一次析构函数 → 剩余对象的资源泄漏
-
释放内存时地址计算错误 → 崩溃
五、完整例子:正确与错误的对比
cpp
#include <iostream>
#include <cstring>
using namespace std;
class StringHolder {
private:
char* data;
size_t len;
public:
StringHolder(const char* s) {
len = strlen(s);
data = new char[len + 1];
strcpy(data, s);
cout << "构造: " << data << " (分配内存)" << endl;
}
~StringHolder() {
cout << "析构: " << data << " (释放内存)" << endl;
delete[] data;
}
void print() const {
cout << "内容: " << data << endl;
}
};
int main() {
cout << "=== 正确示范:new/delete ===" << endl;
StringHolder* s1 = new StringHolder("Hello");
s1->print();
delete s1; // ✅ 调用析构,释放内存
cout << "\n=== 正确示范:new[]/delete[] ===" << endl;
StringHolder* arr = new StringHolder[2] { {"First"}, {"Second"} };
arr[0].print();
arr[1].print();
delete[] arr; // ✅ 调用两次析构
cout << "\n=== 错误示范:new[] 配 delete ===" << endl;
StringHolder* arr2 = new StringHolder[2] { {"A"}, {"B"} };
delete arr2; // ❌ 只调用一次析构!第二个对象内存泄漏
// 程序可能崩溃,或者看似正常但内存泄漏
return 0;
}
输出(正确部分):
text
=== 正确示范:new/delete ===
构造: Hello (分配内存)
内容: Hello
析构: Hello (释放内存)
=== 正确示范:new[]/delete[] ===
构造: First (分配内存)
构造: Second (分配内存)
内容: First
内容: Second
析构: Second (释放内存)
析构: First (释放内存)
错误示范部分的行为是未定义的。
六、placement new:在已分配内存上构造对象
有一种特殊情况:placement new 允许在已有的内存上构造对象(不分配新内存)。
cpp
#include <iostream>
#include <new> // placement new 需要这个头文件
using namespace std;
class Point {
public:
int x, y;
Point(int a, int b) : x(a), y(b) {
cout << "Point 构造: (" << x << "," << y << ")" << endl;
}
~Point() {
cout << "Point 析构" << endl;
}
};
int main() {
// 分配原始内存(不构造对象)
void* mem = operator new(sizeof(Point));
cout << "分配了原始内存,地址: " << mem << endl;
// 在已有内存上构造对象
Point* p = new(mem) Point(10, 20);
// 使用对象
cout << "p->x = " << p->x << ", p->y = " << p->y << endl;
// 手动调用析构
p->~Point();
// 释放原始内存
operator delete(mem);
return 0;
}
使用场景:
-
内存池实现(预分配一大块内存,重复使用)
-
共享内存中的对象构造
-
高性能场景(避免重复分配/释放)
七、常见错误总结
错误1:混用 malloc/free 和 new/delete
cpp
// ❌ 各种混用都是未定义行为
Student* s1 = (Student*)malloc(sizeof(Student));
delete s1;
Student* s2 = new Student("A", 1);
free(s2);
Student* s3 = new Student[10];
delete s3; // 应该是 delete[]
错误2:忘记 delete 导致内存泄漏
cpp
void leak() {
Student* s = new Student("临时", 0);
// 忘记 delete s; → 内存泄漏
}
错误3:重复 delete
cpp
Student* s = new Student("A", 1);
delete s;
delete s; // ❌ 未定义行为(double free)
错误4:delete 后继续使用指针
cpp
Student* s = new Student("A", 1);
delete s;
s->speak(); // ❌ 悬空指针,未定义行为
// 应该 s = nullptr;
八、最佳实践建议
-
永远配对使用 :
new配delete,new[]配delete[],malloc配free -
优先使用智能指针 (
unique_ptr、shared_ptr)→ 后续章节会讲 -
delete 后立即置空 :
delete p; p = nullptr;防止二次释放 -
避免手动 new/delete :能用
vector、string就别自己管理内存 -
类管理资源时遵循五法则:析构、拷贝构造、拷贝赋值、移动构造、移动赋值
九、这一篇的收获
你现在应该理解:
-
new调构造函数 + 分配内存 ,delete调析构函数 + 释放内存 -
malloc/free只处理内存,不处理构造/析构 -
必须配对使用 :
new配delete,new[]配delete[] -
混用是未定义行为:可能崩溃或资源泄漏
-
placement new:在已有内存上构造对象,用于内存池等高级场景
💡 小作业:写一个
Timer类,构造函数中记录开始时间,析构函数中输出"对象存活了 X 毫秒"。用new创建对象,故意忘记delete,观察析构函数是否被调用。然后用delete正确释放,对比行为。
下一篇预告:第29篇《定位new(placement new):在指定内存上构造对象》------深入 placement new 的用法,结合内存池技术,展示如何手动控制对象的构造位置和生命周期。