C++实现自定义的`new`/`delete`：在资源受限环境中的内存管理

admin
Fri, 28 Nov 2025 07:58:16 +0000
C++, c, new, 自定义

好的，下面就以讲座的形式，详细介绍C++中自定义new/delete，以及其在资源受限环境下的内存管理应用。 C++自定义new/delete：资源受限环境下的内存管理 大家好！今天我们来深入探讨C++中的内存管理，特别是如何自定义new和delete操作符，以及它们在资源受限环境中发挥的作用。 1. 默认的new/delete及其局限性 首先，我们回顾一下C++中默认的new和delete。当我们使用new来分配内存时，实际上是调用了全局的operator new函数。同样，delete操作符会调用全局的operator delete函数来释放内存。 // 全局 operator new 的声明 void* operator new(std::size_t size) throw(std::bad_alloc); // 全局 operator delete 的声明 void operator delete(void* ptr) throw(); 这些全局的operator new和operator delete通常由C++标准库提供，它们底层依赖于操作系统提供的内存分配机制（比如ma …

继续阅读“C++实现自定义的`new`/`delete`：在资源受限环境中的内存管理”

The post C++实现自定义的`new`/`delete`：在资源受限环境中的内存管理 first appeared on 智猿学院-前后端，数据库，人工智能，云计算等领域前沿技术讲座.

好的，下面就以讲座的形式，详细介绍C++中自定义new/delete，以及其在资源受限环境下的内存管理应用。

C++自定义new/delete：资源受限环境下的内存管理

大家好！今天我们来深入探讨C++中的内存管理，特别是如何自定义new和delete操作符，以及它们在资源受限环境中发挥的作用。

1. 默认的new/delete及其局限性

首先，我们回顾一下C++中默认的new和delete。当我们使用new来分配内存时，实际上是调用了全局的operator new函数。同样，delete操作符会调用全局的operator delete函数来释放内存。

// 全局 operator new 的声明
void* operator new(std::size_t size) throw(std::bad_alloc);

// 全局 operator delete 的声明
void operator delete(void* ptr) throw();

这些全局的operator new和operator delete通常由C++标准库提供，它们底层依赖于操作系统提供的内存分配机制（比如malloc和free）。

然而，在资源受限的环境中，比如嵌入式系统、游戏引擎或者高性能服务器中，默认的new/delete可能存在以下局限性：

性能开销大： 默认的内存分配器通常是通用的，为了处理各种情况，它们可能引入额外的锁、内存碎片管理等开销，这在性能敏感的应用中是不可接受的。

内存碎片： 频繁地分配和释放不同大小的内存块会导致内存碎片，降低内存利用率，甚至导致分配失败。

不可预测性： 默认的内存分配器可能无法提供确定性的分配时间，这在实时系统中是致命的。

缺乏控制： 无法针对特定应用场景进行优化，比如预先分配内存池、使用特定的分配策略等。

内存泄漏检测困难： 在复杂系统中，内存泄漏难以追踪，自定义内存管理可以方便地添加内存追踪功能。

因此，在资源受限的环境中，我们需要自定义new和delete来克服这些局限性。

2. 自定义new/delete的基本方法

自定义new和delete的核心在于重载operator new和operator delete函数。你可以在全局作用域、类作用域甚至命名空间作用域中重载它们。

2.1 全局作用域重载

在全局作用域重载operator new和operator delete会影响所有使用new和delete的代码。这通常不推荐，因为它可能会与其他库或代码产生冲突。

#include <iostream>
#include <cstdlib> // for malloc and free

void* operator new(std::size_t size) throw(std::bad_alloc) {
    std::cout << "Global new called, size: " << size << std::endl;
    void* p = std::malloc(size);
    if (!p) {
        throw std::bad_alloc();
    }
    return p;
}

void operator delete(void* ptr) throw() {
    std::cout << "Global delete called" << std::endl;
    std::free(ptr);
}

int main() {
    int* p = new int(10);
    delete p;
    return 0;
}

2.2 类作用域重载

在类作用域重载operator new和operator delete只会影响该类的对象的内存分配和释放。这是最常见的自定义new/delete的方式。

#include <iostream>
#include <cstdlib>

class MyClass {
public:
    void* operator new(std::size_t size) throw(std::bad_alloc) {
        std::cout << "MyClass new called, size: " << size << std::endl;
        void* p = std::malloc(size);
        if (!p) {
            throw std::bad_alloc();
        }
        return p;
    }

    void operator delete(void* ptr) throw() {
        std::cout << "MyClass delete called" << std::endl;
        std::free(ptr);
    }

private:
    int data;
};

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

2.3 placement new 和 delete

除了重载基本的 operator new 和 operator delete，C++ 还提供了 placement new，允许你在已分配的内存上构造对象。这在自定义内存管理中非常有用，例如在内存池中创建对象。

#include <iostream>
#include <cstdlib>

class MyClass {
public:
    MyClass(int value) : data(value) {}

    ~MyClass() {
        std::cout << "MyClass destructor called" << std::endl;
    }

private:
    int data;
};

int main() {
    // 分配一块原始内存
    void* buffer = std::malloc(sizeof(MyClass));

    // 使用 placement new 在 buffer 上构造 MyClass 对象
    MyClass* obj = new (buffer) MyClass(42);

    // 显式调用析构函数
    obj->~MyClass();

    // 释放内存
    std::free(buffer);

    return 0;
}

Placement new 实际上并没有分配内存，它只是在已有的内存上调用构造函数。因此，你需要手动调用析构函数，并使用 std::free 或自定义的释放函数来释放内存。 Placement delete 并不存在，因为placement new 没有分配内存，所以不需要释放。 但是需要手动调用析构函数。

3. 资源受限环境下的内存管理策略

在资源受限的环境中，我们通常会采用以下内存管理策略：

3.1 静态内存分配

静态内存分配是在编译时就确定内存大小和位置的分配方式。它可以避免动态内存分配的开销和不确定性。

全局变量/静态变量： 将对象声明为全局变量或静态变量，它们的生命周期贯穿整个程序，内存分配在程序启动时完成。

预分配缓冲区： 预先分配一块大的缓冲区，然后在程序运行时从中分配小块内存。

优点：

速度快，没有动态分配的开销。

确定性，避免了分配失败的风险。

缺点：

灵活性差，需要在编译时确定内存大小。

可能造成内存浪费，如果预分配的内存没有被充分利用。

3.2 内存池

内存池是一种预先分配一块大的连续内存区域，然后将其划分为固定大小的块，用于快速分配和释放对象的内存管理策略。

#include <iostream>
#include <vector>
#include <cstddef>  // for std::size_t

class MemoryPool {
public:
    MemoryPool(std::size_t blockSize, std::size_t poolSize)
        : blockSize_(blockSize), poolSize_(poolSize), freeBlocks_(poolSize) {
        memory_ = std::malloc(blockSize_ * poolSize_);
        if (!memory_) {
            throw std::bad_alloc();
        }

        // 将内存块链接成链表
        char* block = static_cast<char*>(memory_);
        for (std::size_t i = 0; i < poolSize_ - 1; ++i) {
            *reinterpret_cast<void**>(block) = block + blockSize_;
            block += blockSize_;
        }
        *reinterpret_cast<void**>(block) = nullptr; // 最后一个块指向 nullptr
        freeList_ = memory_;
    }

    ~MemoryPool() {
        std::free(memory_);
    }

    void* allocate() {
        if (freeList_ == nullptr) {
            return nullptr; // 内存池已耗尽
        }

        void* block = freeList_;
        freeList_ = *reinterpret_cast<void**>(freeList_);
        --freeBlocks_;
        return block;
    }

    void deallocate(void* ptr) {
        if (!ptr) return;

        // 将释放的块添加到链表头部
        *reinterpret_cast<void**>(ptr) = freeList_;
        freeList_ = ptr;
        ++freeBlocks_;
    }

    std::size_t getFreeBlocks() const {
        return freeBlocks_;
    }

private:
    void* memory_;        // 指向内存池的起始地址
    void* freeList_;      // 指向空闲块链表的头部
    std::size_t blockSize_; // 每个块的大小
    std::size_t poolSize_;  // 内存池中块的数量
    std::size_t freeBlocks_; //空闲块数量
};

// 自定义 new 和 delete 使用内存池
class MyClass {
public:
    MyClass(int value) : data(value) {}

    static void* operator new(std::size_t size) {
        return pool.allocate();
    }

    static void operator delete(void* ptr) {
        pool.deallocate(ptr);
    }

private:
    int data;
    static MemoryPool pool;
};

// 初始化内存池，在程序开始前
MemoryPool MyClass::pool(sizeof(MyClass), 10); // 10个 MyClass 对象

int main() {
    MyClass* obj1 = new MyClass(1);
    MyClass* obj2 = new MyClass(2);

    std::cout << "Free blocks: " << MyClass::pool.getFreeBlocks() << std::endl; // 输出 8

    delete obj1;
    delete obj2;

    std::cout << "Free blocks: " << MyClass::pool.getFreeBlocks() << std::endl; // 输出 10

    return 0;
}

优点：

快速分配和释放，避免了动态分配的开销。

减少内存碎片，所有块大小相同。

可以控制内存分配的行为，比如限制最大分配数量。

缺点：

只适用于分配固定大小的对象。

需要预先确定内存池的大小。

3.3 定制分配器 (Custom Allocators)

C++ 标准库允许你创建自定义的分配器，并将其与标准容器一起使用。这允许你控制容器的内存分配行为。

#include <iostream>
#include <vector>
#include <memory> // allocator

template <typename T>
class MyAllocator {
public:
    using value_type = T;
    using pointer = T*;
    using const_pointer = const T*;
    using reference = T&;
    using const_reference = const T&;
    using size_type = std::size_t;
    using difference_type = std::ptrdiff_t;

    MyAllocator() noexcept {}

    template <typename U>
MyAllocator(const MyAllocator&) noexcept {}

    pointer allocate(size_type n) {
        std::cout << "Allocating " << n << " elements" << std::endl;
        pointer p = static_cast<pointer>(std::malloc(n * sizeof(T)));
        if (!p) {
            throw std::bad_alloc();
        }
        return p;
    }

    void deallocate(pointer p, size_type n) {
        std::cout << "Deallocating " << n << " elements" << std::endl;
        std::free(p);
    }
};

template <typename T, typename U>
bool operator==(const MyAllocator<T>&, const MyAllocator&) noexcept {
    return true;
}

template <typename T, typename U>
bool operator!=(const MyAllocator<T>&, const MyAllocator&) noexcept {
    return false;
}

int main() {
    std::vector<int, MyAllocator<int>> myVector; // 使用自定义分配器
    myVector.push_back(10);
    myVector.push_back(20);

    return 0;
}

优点：

可以与标准容器无缝集成。

灵活性高，可以根据需要定制分配行为。

缺点：

需要实现分配器接口，比较复杂。

可能需要考虑线程安全问题。

3.4 双缓冲/循环缓冲

在某些应用中，比如音视频处理，可以使用双缓冲或循环缓冲来避免频繁的内存分配和释放。

双缓冲： 使用两个缓冲区，一个用于写入数据，另一个用于读取数据。当一个缓冲区写满时，切换到另一个缓冲区。

循环缓冲： 使用一个固定大小的缓冲区，当写入数据到达缓冲区末尾时，从头开始覆盖。

优点：

避免了频繁的内存分配和释放。

可以实现实时数据处理。

缺点：

需要预先确定缓冲区的大小。

可能存在数据覆盖的风险。

4. 内存泄漏检测

自定义new/delete可以方便地添加内存泄漏检测功能。例如，可以维护一个已分配内存块的列表，在程序退出时检查是否有未释放的内存块。

#include <iostream>
#include <cstdlib>
#include <vector>
#include <algorithm>

struct MemoryBlock {
    void* address;
    std::size_t size;
};

std::vector<MemoryBlock> allocatedBlocks;

void* operator new(std::size_t size) throw(std::bad_alloc) {
    void* p = std::malloc(size);
    if (!p) {
        throw std::bad_alloc();
    }
    allocatedBlocks.push_back({p, size});
    return p;
}

void operator delete(void* ptr) throw() {
    auto it = std::find_if(allocatedBlocks.begin(), allocatedBlocks.end(),
                            [ptr](const MemoryBlock& block) { return block.address == ptr; });
    if (it != allocatedBlocks.end()) {
        allocatedBlocks.erase(it);
        std::free(ptr);
    } else {
        std::cerr << "Error: Attempting to delete unallocated memory" << std::endl;
    }
}

void DumpMemoryLeaks() {
    if (!allocatedBlocks.empty()) {
        std::cerr << "Memory Leaks Detected:" << std::endl;
        for (const auto& block : allocatedBlocks) {
            std::cerr << "  Address: " << block.address << ", Size: " << block.size << std::endl;
        }
    } else {
        std::cout << "No memory leaks detected." << std::endl;
    }
}

// 示例类
class MyClass {
public:
    MyClass(int value) : data(value) {}
private:
    int data;
};

int main() {
    MyClass* obj1 = new MyClass(10);
    // 故意不释放 obj1，造成内存泄漏
    //delete obj1;

    DumpMemoryLeaks(); // 在程序结束时检测内存泄漏
    return 0;
}

5. 选择合适的内存管理策略

选择合适的内存管理策略取决于具体的应用场景和资源限制。

策略 | 优点 | 缺点 | 适用场景
静态内存分配 | 速度快，确定性 | 灵活性差，可能造成内存浪费 | 内存需求固定，对性能要求高的场景
内存池 | 快速分配和释放，减少内存碎片，可控 | 只适用于固定大小的对象，需要预先确定大小 | 大量分配和释放固定大小对象的场景，比如游戏引擎
定制分配器 | 与标准容器集成，灵活性高 | 实现复杂，可能需要考虑线程安全 | 需要定制容器内存分配行为的场景
双缓冲/循环缓冲 | 避免频繁分配和释放，实现实时数据处理 | 需要预先确定大小，可能存在数据覆盖的风险 | 音视频处理，实时数据采集等需要避免频繁分配的场景

6. 其他注意事项

对齐： 自定义内存分配器需要保证分配的内存块满足对齐要求，以避免性能问题。可以使用std::align函数来对齐内存。

异常安全： 确保自定义new和delete是异常安全的，即在构造函数抛出异常时，能够正确地释放已分配的内存。

线程安全： 在多线程环境下，需要考虑线程安全问题，可以使用锁或其他同步机制来保护内存分配器。

调试： 自定义内存管理可能会使调试更加困难，需要使用合适的调试工具和技术来定位问题。

总结：

自定义new和delete是C++中强大的内存管理工具，在资源受限环境中尤为重要。通过选择合适的内存管理策略，可以提高程序的性能、可靠性和可控性。请记住，选择合适的策略需要仔细评估应用场景和资源限制，并进行充分的测试和验证。

定制内存管理是关键

掌握自定义 new 和 delete 的方法，并了解各种内存管理策略的优缺点，可以帮助你在资源受限环境中构建更高效、更稳定的应用程序。

根据实际情况选择策略

静态内存分配、内存池、定制分配器和双缓冲/循环缓冲等策略各有千秋，选择哪一种取决于具体的应用场景和资源限制。

代码示例的价值

通过代码示例加深理解，能够更好地掌握自定义内存管理的技术，并在实际项目中灵活应用。

更多IT精英技术系列讲座，到智猿学院