C++ 动态内存管理详讲
1. 四个全局函数的定义与作用
这四个函数只负责空间的开辟和释放,不会调构造和析构
(1) ::operator new
 
cpp
void* operator new(size_t size); // 全局版本
-  功能:分配 size字节的未初始化内存。
-  底层实现:调用 malloc(size)。
-  调用场景: -  直接调用: void* p = ::operator new(100);(手动分配 100 字节)。
-  间接调用: new T会先调用operator new(sizeof(T)),然后调T构造函数初始化成员变量。
 
-  
(2) ::operator delete
 
cpp
void operator delete(void* ptr) noexcept; // 全局版本
-  功能:释放 operator new分配的内存。
-  底层实现:调用 free(ptr)。
-  调用场景: -  直接调用: ::operator delete(p),释放空间;。
-  间接调用: delete p会先调用析构函数释放资源内存空间,再调用operator delete(p)释放对象本身空间。
 
-  
(3) ::operator new[]
 
cpp
void* operator new[](size_t size); // 全局版本
-  功能:分配 size字节的未初始化内存(用于数组)。
-  底层实现:封装调用 operator new(size),更底层就是malloc。
-  调用场景: -  直接调用: void* p = ::operator new[](100);。
-  间接调用: new T[n]会先调用operator new[](n * sizeof(T) + 额外空间),然后调用n次构造函数。
 
-  
(4) ::operator delete[]
 
cpp
void operator delete[](void* ptr) noexcept; // 全局版本
-  功能:释放 operator new[]分配的内存。
-  底层实现:调用 operator delete(ptr)(即free)。
-  调用场景: -  直接调用: ::operator delete[](p);。
-  间接调用: delete[] p会先调用n次析构函数释放资源,再调用operator delete[](p)释放整块对象本身空间。
 
-  
2. 与 new/delete 的关系
 
| 操作 | 底层调用 | 额外行为 | 
|---|---|---|
| new T | ::operator new(sizeof(T)) | 调用构造函数 | 
| delete p | ::operator delete(p) | 先调用析构函数 | 
| new T[n] | ::operator new[](n * sizeof(T) + 额外) | 调用 n次构造函数 | 
| delete[] p | ::operator delete[](p) | 先调用 n次析构函数 | 
关键区别
-  operator new/delete只负责 内存分配/释放,不涉及构造/析构。
-  new/delete是 更高层的操作,会额外调用构造/析构函数。
3. 为什么需要额外空间?
当使用 new T[n] 时:
-  如果 T是内置类型(如int、char),在释放时只需要释放内存,无需调用析构函数,因此开辟空间时可能不需要额外空间。
-  如果 T是自定义类型(有析构函数),delete[]必须知道数组大小n,才能正确调用n次析构函数。
问题:delete[] 如何知道 n?
 答案:编译器会在 operator new[] 分配的内存中隐式插入 n,通常是存储在返回指针之前的额外空间里。
4. 额外空间的存储方式
(1) 典型的内存布局
[对象数量 n][T 对象 0][T 对象 1]...[T 对象 n-1]↑p(返回给用户的指针)
-  p是用户得到的指针,指向第一个对象T[0]。
-  p - sizeof(size_t)的位置可能存储n(具体偏移量由编译器决定)。
(2) 分配的总大小
operator new[] 实际分配的内存大小:
cpp
总大小 = sizeof(T) * n + 额外空间(存储 n 或其他元数据)=sz(传参) + sizeof(n)
-  额外空间的大小: -  可能是 sizeof(size_t)(存储n)。
 
-  
5. delete[] 如何利用额外空间?
 
当调用 delete[] p 时:
-  定位 n:-  编译器生成代码,从 p - offset处读取n(offset是编译器决定的,通常是sizeof(size_t))。
 
-  
-  调用析构函数: -  逆序调用 n次析构函数(从p[n-1]到p[0])。
 
-  
-  释放内存: -  调用 operator delete[](original_ptr),其中original_ptr是operator new[]返回的原始指针(比p小,指向分配块的起始地址)。
 
-  
6. 关键问题解答
(1) 为什么 new[] 和 delete[] 必须配对使用?
 
-  new[]可能分配额外空间存储n,而delete[]依赖这个信息调用析构函数。
(2) 如何手动模拟 new[] 和 delete[]?
 
cpp
// 手动实现 new[]
void* operator new[](size_t size) {size_t total_size = size + sizeof(size_t);  // 额外空间存 nvoid* ptr = malloc(total_size);*(size_t*)ptr = size / sizeof(T);  // 存储 nreturn (char*)ptr + sizeof(size_t);  // 返回用户指针
}// 手动实现 delete[]
void operator delete[](void* p) noexcept {size_t* hidden_n = (size_t*)p - 1;size_t n = *hidden_n;for (size_t i = n; i > 0; --i) {((T*)p)[i - 1].~T();  // 调用析构函数}free(hidden_n);  // 释放原始指针
} 
 
关键字
1. new 和 delete 的行为
 
(1) new 的流程(T* p = new T(args);)
 
-  调用 operator new(sizeof(T))开辟空间。
-  在分配的内存上调用 T的构造函数初始化成员变量(如果是内置类型,无构造步骤)。
-  返回指向对象的指针。 
(2) delete 的流程(delete p;)
 
-  调用 p指向对象的析构函数释放资源空间(如果是内置类型,无析构步骤)。
-  调用 operator delete(p)释放对象本身空间。
2. new[] 和 delete[] 的行为
 
(1) new[] 的流程(T* p = new T[n];)
 
-  调用 operator new[](sizeof(T) * n)分配内存。-  额外空间可能存储对象数量(编译器实现相关)。 
 
-  
-  对每个元素依次调用构造函数初始化成员变量(从 p[0]到p[n-1])。
(2) delete[] 的流程(delete[] p;)
 
-  对每个元素逆序调用析构函数释放资源空间(从 p[n-1]到p[0])。
-  调用 operator delete[](p)释放对象本身空间。
