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 是内置类型(如 intchar在释放时只需要释放内存,无需调用析构函数,因此开辟空间时可能不需要额外空间

  • 如果 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 时:

  1. 定位 n

    • 编译器生成代码,从 p - offset 处读取 noffset 是编译器决定的,通常是 sizeof(size_t))。

  2. 调用析构函数

    • 逆序调用 n 次析构函数(从 p[n-1] 到 p[0])。

  3. 释放内存

    • 调用 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);  // 额外空间存 n
    void* ptr = malloc(total_size);
    *(size_t*)ptr = size / sizeof(T);  // 存储 n
    return (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);
  1. 调用 operator new(sizeof(T)) 开辟空间。

  2. 在分配的内存上调用 T 的构造函数初始化成员变量(如果是内置类型,无构造步骤)。

  3. 返回指向对象的指针。

(2) delete 的流程(delete p;
  1. 调用 p 指向对象的析构函数释放资源空间(如果是内置类型,无析构步骤)。

  2. 调用 operator delete(p) 释放对象本身空间。


2. new[] 和 delete[] 的行为

(1) new[] 的流程(T* p = new T[n];
  1. 调用 operator new[](sizeof(T) * n) 分配内存。

    • 额外空间可能存储对象数量(编译器实现相关)。

  2. 对每个元素依次调用构造函数初始化成员变量(从 p[0] 到 p[n-1])。

(2) delete[] 的流程(delete[] p;
  1. 对每个元素逆序调用析构函数释放资源空间(从 p[n-1] 到 p[0])。

  2. 调用 operator delete[](p) 释放对象本身空间。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值