MST

星途 面试题库

面试题:C++中delete与delete []在底层实现机制及与内存分配器交互上的区别

从C++底层实现机制角度出发,阐述`delete`和`delete []`在与内存分配器交互方面的差异。假设我们自定义了一个内存分配器`MyAllocator`,如何确保在使用`delete`和`delete []`时,该分配器能够正确地释放内存,并且析构函数被正确调用?请给出实现思路及关键代码片段。
37.5万 热度难度
编程语言C++

知识考点

AI 面试

面试题答案

一键面试

deletedelete [] 与内存分配器交互的差异

  1. delete
    • delete 用于释放单个对象的内存。在C++ 底层,它首先调用对象的析构函数(如果有),然后调用内存分配器的释放函数来释放该对象占用的内存空间。例如,对于 MyClass* obj = new MyClass(); delete obj;,会先调用 obj 的析构函数 ~MyClass(),然后调用内存分配器将 obj 所指向的内存块归还给堆。
  2. delete []
    • delete [] 用于释放数组对象的内存。它会对数组中的每个元素调用析构函数(如果有),然后调用内存分配器释放整个数组占用的连续内存空间。对于 MyClass* arr = new MyClass[10]; delete [] arr;,会依次调用 arr[0]arr[9] 的析构函数 ~MyClass(),然后调用内存分配器释放这 10 个 MyClass 对象占用的连续内存块。

确保自定义内存分配器正确释放内存及调用析构函数的实现思路

  1. 重载全局 operator newoperator delete:为了让自定义内存分配器 MyAllocator 能被 deletedelete [] 正确调用,需要重载全局的 operator newoperator delete 函数。
  2. 跟踪对象数量(针对 delete []:在分配数组内存时,需要额外记录数组中对象的数量,以便在 delete [] 时正确调用每个对象的析构函数。一种常见的做法是在分配的内存块头部存储对象数量。

关键代码片段

#include <iostream>
class MyAllocator {
public:
    void* allocate(size_t size) {
        // 实际的内存分配逻辑,例如调用系统的malloc
        return ::operator new(size);
    }
    void deallocate(void* ptr) {
        // 实际的内存释放逻辑,例如调用系统的free
        ::operator delete(ptr);
    }
};

MyAllocator myAllocator;

// 重载全局 operator new
void* operator new(size_t size) {
    return myAllocator.allocate(size);
}

// 重载全局 operator delete
void operator delete(void* ptr) {
    myAllocator.deallocate(ptr);
}

// 重载全局 operator new[]
void* operator new[](size_t size) {
    // 这里额外分配4字节用于存储对象数量(假设是32位系统,4字节足够存储数组元素数量)
    size_t totalSize = size + sizeof(size_t);
    void* ptr = myAllocator.allocate(totalSize);
    // 在分配内存块头部存储对象数量
    *(size_t*)ptr = size / sizeof(char); // 简单假设对象大小为1字节,实际应根据对象类型计算
    return static_cast<char*>(ptr) + sizeof(size_t);
}

// 重载全局 operator delete[]
void operator delete[](void* ptr) {
    // 计算出存储对象数量的位置
    char* realPtr = static_cast<char*>(ptr) - sizeof(size_t);
    size_t numElements = *(size_t*)realPtr;
    // 调用每个对象的析构函数
    for (size_t i = 0; i < numElements; ++i) {
        (static_cast<char*>(ptr) + i * sizeof(char))->~char(); // 假设对象类型为char,实际应根据对象类型调用析构函数
    }
    myAllocator.deallocate(realPtr);
}

class MyClass {
public:
    MyClass() { std::cout << "MyClass constructor" << std::endl; }
    ~MyClass() { std::cout << "MyClass destructor" << std::endl; }
};

int main() {
    MyClass* obj = new MyClass();
    delete obj;

    MyClass* arr = new MyClass[3];
    delete [] arr;

    return 0;
}

上述代码展示了如何通过重载全局的 operator newoperator delete 函数,让自定义内存分配器 MyAllocator 能够在 deletedelete [] 操作中正确释放内存并调用析构函数。注意在实际应用中,对于 operator new[]operator delete[] 中对象大小及析构函数调用的处理应根据实际对象类型准确计算和操作。