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)
释放对象本身空间。