new 和 delete 是 C++ 中非常重要的两个关键字,其作用是实现动态对象的管理。正确掌握它们的使用方法对于有效管理程序的内存、提高性能,以及避免内存泄漏等问题至关重要,是编写健壮 C++ 程序的核心技能。
1. 创建/销毁对象
在 C++ 中,new
和 delete
运算符常用于动态对象的创建和销毁。这一节,我们需要了解:
- 单个对象的创建和销毁过程
- 对象数组的创建和销毁过程
#if 1 #include <iostream> using namespace std; struct Demo { Demo() { cout << "Demo 构造函数" << endl; pArr = new int[100]; } void do_logic() { cout << "Demo Logic 函数" << endl; } ~Demo() { cout << "Demo 析构函数" << endl; delete pArr; } int* pArr; }; // 1. 单个对象 void test01() { Demo* demo = new Demo; delete demo; } // 2. 对象数组 void test02() { Demo* demo = new Demo[3]; delete[] demo; // delete demo; // free(demo); int* data = new int[3]; // delete[] data; // delete data; // free(data); // 为什么 demo 使用 delete 或 free 会导致程序崩溃? // 为什么 data 使用 delete 或 free 不会导致程序崩溃? } int main() { test02(); return 0; } #endif
对象数组的内存结构左图,内置类型数组的内存结构如右图。
2. 分配/释放内存
在 C 语言中,我们使用 malloc 和 free 实现象内存的分配和释放。虽然这两个函数在 C++ 中也适用,但是我们很少直接使用,而是使用 operator new 和 operator delete 函数。接下来,我们通过三个问题,来理解 operator new 和 operator delete 函数的用法和更多细节。
- new/delete 和 operator new/delete 的区别是什么?
- malloc/free 和 operator new/delete 的区别是什么?
- new handler 如何使用?
#if 0 #include <iostream> using namespace std; struct Demo { Demo() { cout << "Demo 构造函数" << endl; } ~Demo() { cout << "Demo 析构函数" << endl; } }; // 1. new/delete 和 operator new/delete 的区别是什么? // 1.1 new/delete 是运算符,用于对象的实例化和销毁 // 1.2 operator new/delete 是函数,用于内存的申请和释放 // 1.2 new/delete 的内存申请通过调用 operator new/delete 实现 void test01() { /* 00007FF7499825E1 call operator new (07FF74998104Bh) 00007FF7499825FE call Demo::Demo (07FF7499812DAh) */ Demo* demo = new Demo; /* 00007FF7660423EE call Demo::~Demo (07FF766041082h) 00007FF76604240D call operator delete (07FF7660413EDh) */ delete demo; } // 2. malloc/free 和 operator new/delete 的区别是什么? // 2.1 两者都是用于内存申请和释放函数 // 2.2 operator new/delete 是对 malloc/free 函数的封装 // 2.3 内存申请失败时,malloc 返回空指针,operator new 则执行 new handler,或抛出异常 void test02() { void* p1 = malloc(100); free(p1); void* p2 = operator new(100); operator delete(p2); } void my_new_handler() { cout << "my new handler 函数调用" << endl; } // 3. new handler 如何使用? // 3.1 new handler 是内存申请失败时回调的处理函数 // 3.2 使用 set_new_handler 设置回调处理函数 void(*)() // 3.3 该函数内部一般执行释放资源、日志记录、抛出异常等操作 // 注意:提供 new_handler 之后,可能会陷入无限循环 void test03() { // 设置 new handler 函数 set_new_handler(my_new_handler); void* ptr = operator new(100000000000000000); } int main() { test03(); return 0; } #endif
3. 初始化/清理对象
我们在进行对象创建时,由 new 自动申请内存,并进行对象的初始化。假设,我已有内存,只希望在我持有的内存上初始化对象(即:在指定内存位置调用对象构造函数),这个如何实现?
在 C++ 中,构造函数不能够直接手动调用,但可以通过 placement new 来实现。
有对象初始化就会对应对象清理,由于析构函数是可以直接调用,对于在指定内存位置清理对象,就相对比较简单。
所以,这一节,我们需要学习以下几个问题:
- 如何在指定内存位置构造和清理对象?
- new 和 placement new 的异同是什么?
- placement new 使用时,有什么注意点?
#if 1 #include <iostream> using namespace std; struct Demo { Demo() { cout << "Demo 构造函数" << endl; } Demo(int, int) { cout << "Demo 有参构造" << endl; } void do_logic() { cout << "Demo Logic 函数" << endl; } ~Demo() { cout << "Demo 析构函数" << endl; } }; // 1. 如何在指定内存位置构造和清理对象? void test01() { // 1. 在堆上 void* ptr = operator new(sizeof(Demo)); Demo* demo = new(ptr) Demo(10, 20); demo->do_logic(); demo->~Demo(); operator delete(ptr); // 2. 在栈上 char buf[32] = { 0 }; Demo* d = new(buf) Demo; d->do_logic(); d->~Demo(); } // 2. new 和 placement new 的异同是什么? // 2.1 new 和 placement new 两者都是运算符,placement new 是一个带参数版本的 new // 2.2 两者都遵循对象的构造函数:operator new + 构造函数 // 2.3 new 分配内存 + 构造函数,placement new 不分配内存 + 构造函数 void test02() { char buf[32] = { 0 }; Demo* d = new(buf) Demo; } // 3. placement new 使用时,有什么注意点? // 3.1 确保内存有效性,一定要保证内存大小和位置合法,避免越界操作 // 3.2 内存对齐,确保分配的内存地址满足对象的对齐要求 // 3.3 小心处理内存管理,例如:避免重复构造,可能会导致内存泄漏 struct Sample { Sample() { cout << "Sample 构造函数" << endl; int* p = new int[100000]; } ~Sample() { if (p != nullptr) { delete[] p; } cout << "Sample 析构函数" << endl; } int* p{ nullptr }; }; void test03() { char buf[32] = { 0 }; Sample* sample = nullptr; for (;;) { sample = new (buf) Sample; } sample->~Sample(); } int main() { test03(); return 0; } #endif