目录
- C++ new 和 delete
- 1. 基本语法
- new 运算符
- delete 运算符
- 2. 工作原理
- new 的执行步骤
- delete 的执行步骤
- 3. 深入理解 new 和 delete
- 原始内存操作
- 定位 new(Placement New)
- 4. 数组的动态分配
- 分配与释放数组
- 数组初始化
- 5.new和delete的实现原理
- 5.1 内置类型
- 5.2 自定义类型
- 6. 常见问题与注意事项
- 内存泄漏
- 悬空指针
- 重复释放
- 混用 delete 和 delete[]
- 7. 自定义内存管理
- 重载全局 operator new 和 operator delete
- 类特定的内存管理
- 8. 与C语言内存管理的对比
- 总结
C++ new 和 delete
在C++中,new
和 delete
是用于动态内存分配和释放的运算符。
它们允许程序在运行时管理堆内存,是实现动态数据结构(如链表、树)和灵活资源管理的基础。
1. 基本语法
new 运算符
- 分配单个对象:
Type* ptr = new Type; // 默认初始化(内置类型值未定义) Type* ptr = new Type(value); // 直接初始化 Type* ptr = new Type{value}; // 列表初始化(C++11起)
- 分配数组:
Type* arr = new Type[size]; // 分配包含size个元素的数组
- 值初始化:
Type* ptr = new Type(); // 值初始化为0或默认构造函数
delete 运算符
- 释放单个对象:
delete ptr; // 调用析构函数(如有)并释放内存
- 释放数组:
delete[] arr; // 释放数组内存,需使用[]告知编译器释放多个对象
2. 工作原理
new 的执行步骤
- 内存分配:调用
operator new
(或operator new[]
用于数组)分配原始内存。 - 构造函数调用:在分配的内存上调用对象的构造函数(若为类类型)。
delete 的执行步骤
- 析构函数调用:对对象调用析构函数(若为类类型)。
- 内存释放:调用
operator delete
(或operator delete[]
)释放内存。
3. 深入理解 new 和 delete
原始内存操作
operator new
和 operator delete
:
底层函数,可被重载以自定义内存分配行为。
void* operator new(size_t size); // 分配单个对象 void* operator new[](size_t size); // 分配数组 void operator delete(void* ptr); // 释放单个对象 void operator delete[](void* ptr); // 释放数组
示例:
void* raw_memory = operator new(sizeof(int)); // 分配原始内存 int* ptr = static_cast<int*>(raw_memory); // 转换为int指针 *ptr = 42; // 手动赋值 operator delete(ptr); // 释放内存(不调用析构函数)
定位 new(Placement New)
在已分配的内存上构造对象:
void* buffer = operator new(sizeof(MyClass)); // 分配原始内存 MyClass* obj = new (buffer) MyClass(); // 在buffer上构造对象 obj->~MyClass(); // 显式调用析构函数 operator delete(buffer); // 释放内存
4. 数组的动态分配
分配与释放数组
int* arr = new int[10]; // 分配10个int的数组 delete[] arr; // 释放数组 // 多维数组 int** matrix = new int*[5]; // 分配5行 for (int i = 0; i < 5; ++i) { matjavascriptrix[i] = new int[10]; // 每行10列 } // 释放 for (int i = 0; i < 5; ++i) { delete[] matrix[i]; } delete[] matrix;
数组初始化
int* arr = new int[5]{1, 2, 3, 编程4, 5}; // C++11起支持列表初始化
在对数组进行动态分配时,会自动在之前多分配一块空间用来储存所要开辟的空间个数,以便将来用delete释放空间。
5.new和delete的实现原理
5.1 内置类型
如果申请的是内置类型的空间,new和malloc,delete和free基本类似,不同的地方是:
new/delete申请和释放的是单个元素的空间,new[]和delete[]申请的是连续空间,而且new在申请空间失败时会抛异常,malloc会返回NULL。
5.2 自定义类型
new的原理
- 调用operator new函数申请空间
- 在申请的空间上执行构造函数,完成对象的构造
delete的编程客栈原理
- 在空间上执行析构函数,完成对象中资源的清理工作
- 调用operator delete函数释放对象的空间
new T[N]的原理
- 调用operator new[]函数,在operator new[]中实际调用operator new函数完成N个对象空间的申请
- 在申请的空间上执行N次构造函数
delete[]的原理
- 在释放的对象空间上执行N次析构函数,完成N个对象中资源的清理
- 调用operator delete[]释放空间,实际在operator delete[]中调用operator delete来释放空间
6. 常见问题与注意事项
内存泄漏
原因:忘记调用 delete
释放 new
分配的内存。
示例:
void leak() { int* ptr = new int; // 分配内存 // 未释放内存就返回 } // 内存泄漏!
悬空指针
原因:释放内存后仍保留指向该内存的指针。
示例:
int* ptr = new int; delete ptr; *ptr = 10; // 悬空指针解引用,未定义行为
重复释放
原因:多次释放同一块内存。
示例:
int* ptr = new int; delete ptr; delete ptr; // 重复释放,未定义行为
混用 delete 和 delete[]
错误示例:
int* arr = new int[10]; delete arr; // 错误!应使用delete[]
对于内置类型,程序会崩溃;而对于自定义类型,程序不会崩溃。
7. 自定义内存管理
重载全局 operator new 和 operator delete
void* operator new(size_t size) { void* p = malloc(size); // 可添加内存分配日志、性能监控等 return p; } void operator delete(void* p) noexcept { free(p); // 可添加内存释放日志等 }
类特定的内存管理
class MyClass { public: static void* operator new(size_t size) { // 自定义分配逻辑 return ::operator new(size); } static void operator delete(void* p) noexcept { // 自定义释放逻辑 ::operator delete(p); } };
8. 与C语言内存管理的对比
特性 | C++ new/delete | C语言 malloc/free |
---|---|---|
类型安全 | 自动推导类型,无需强制转换 | 需要显式转换(如 (int*)malloc()) |
构造/析构函数 | 自动调用构造函数和析构函数 | 不调用构造/析构函数 |
初始化 | 支持直接初始化和列表初始化 | 仅分配内存,不初始化值 |
数组语法 | 直接使用 new Type[size] | 需要计算总大小(如 malloc(size*sizeof(Type))) |
智能指针支持 | 与标准库智能指针无缝配合 | 需手动封装为智能指针 |
malloc/free和new/delete的共同点是:都是从堆上申请空间,并且需要用户手动释放。
不同的地方是:
- malloc和free是函数,new和delete是操作符
- malloc申请的空间不会初始化,new可以初始化
- malloc申请空间时,需要手动计算空间大小并传递,new只需在其后跟上空间的类型即可, 如果是多个对象,[]中指定对象个数即可
- malloc的返回值为void*, 在使用时必须强转,new不需要,因为new后跟的是空间的类型
- malloc申请空间失败时,返回的是N编程客栈ULL,因此使用时必须判空,new不需要,但是new需要捕获异常
- 申请自定义类型对象时,malloc/free只会开辟空间,编程客栈不会调用构造函数与析构函数,而new在申请空间后会调用构造函数完成对象的初始化,delete在释放空间前会调用析构函数完成空间中资源的清理释放
总结
new
/delete
是C++动态内存分配的核心机制,适用于:
- 需要在运行时确定对象生命周期。
- 实现动态数据结构(如链表、树)。
- 优先使用智能指针:现代C++中,
std::unique_ptr
和std::shared_ptr
能有效避免内存泄漏,提高代码安全性。 - 谨慎手动管理内存:必须确保
new
和delete
配对使用,避免悬空指针和重复释放。
以上为个人经验,希望能给大家一个参考,也希望大家多多支持编程客栈(www.devze.com)。
精彩评论