重载new和delete运算符

Last Updated: 2023-12-29 03:11:35 Friday

-- TOC --

C++推荐使用new来动态申请内存,new本质上是个运算符,也可以被重载。delete也是运算符,要和new一并重载。

// in <new> header
void* operator new(size_t);
void operator delete(void*);
void* operator new[](size_t);
void operator delete[](void*);

为什么要重载new和delete运算符呢?

因为默认new会在进程的虚拟地址空间heap上申请内存,由于内存碎片的存在,new这个操作在某些场景下(游戏,高频交易等)带来的延时是不可接受的。或者是在Windows kernel和嵌入式场景。(Linux kernel是C语言)

In some environments, like the Windows kernel or embedded systems, there is no free store(heap) available to you by default. In other settings, such as game development or high-frequency trading, free store allocations simply involve too much latency, because you’ve delegated its management to the operating system.

重载后,new和delete就可以在代码指定的内存空间(预先已经申请好的)进行特定的操作,可以最大限度的加快速度。

下面是测试代码,mitem对象就是一块4096字节的内存,mem对象包含了一系列mitem,重载后,new和delete都在mem对象内进行内存操作:

#include <cstdio>
#include <stdexcept>


struct mitem {
    // const static can be initialized here
    const static size_t msize{4096};
    std::byte mspace[msize];
};


class mem {
private:
    // const static can be initialized here,
    // if not use const static,
    // you will need a non-default constructor.
    const static int mnum{16};
    bool  space_used[mnum];

public:
    mitem space[mnum];
    void* allocate(size_t size);
    void  free(void* p);
};


void* mem::allocate(size_t size) {
    if (size > mitem::msize)
        throw std::bad_alloc{};
    for (int i{}; i<mnum; ++i)
        if (space_used[i] == false) {
            space_used[i] = true;
            return space[i].mspace;
        }
    throw std::bad_alloc{};
}


void mem::free(void* p) {
    for (int i{}; i<mnum; ++i)
        if (p == space[i].mspace) {
            space_used[i] = false;
            return;
        }
}


// memory
mem heap{};


void* operator new(size_t size) {
    void* p = heap.allocate(size);
    printf("allocate %p, size %zu\n", p, size);
    return p;
}


void operator delete(void* p) {
    if (p == nullptr)
        printf("free nullptr\n");  // never show up
    printf("--1-- free %p\n", p);
    return heap.free(p);
}


void operator delete(void* p, size_t size) {
    if (p == nullptr)
        printf("free nullptr\n");  // never show up
    printf("--2-- free %p, size %zu\n", p, size);
    return heap.free(p);
}


int main(void) {
    try {
        int* a = new int{12};
        printf("a %d\n", *a);
        float* b = new float{1.2345f};
        printf("b %f\n", *b);
        char* c = new char{'c'};
        printf("c %c\n", *c);
        delete a;
        delete b;
        delete c;
    } catch (const std::exception& e) {
        printf("exception 0: %s\n", e.what());
    }

    int* p[24]{};
    try {
        for (int i{}; i<24; ++i)
            p[i] = new int{32};
    } catch (const std::exception& e) {
        printf("exception 1: %s\n", e.what());
        for (int i{}; i<24; ++i) {
            if (p[i] == nullptr)
                printf("try to delete nullptr\n");
            delete p[i];
        }
    }

    try {
        int* p = new int[1234]{};
        p[0] = 1;  // suppress warning
    } catch (const std::exception& e) {
        printf("exception 2: %s\n", e.what());
    }

    return 0;
}

所有的new和delete,都在预先定义好的静态地址空间中进行。

下面是运行效果:

$ g++ -Wall -Wextra new.cpp -o new
$ ./new
allocate 0x4040b0, size 4
a 12
allocate 0x4050b0, size 4
b 1.234500
allocate 0x4060b0, size 1
c c
--2-- free 0x4040b0, size 4
--2-- free 0x4050b0, size 4
--2-- free 0x4060b0, size 1
allocate 0x4040b0, size 4
allocate 0x4050b0, size 4
allocate 0x4060b0, size 4
allocate 0x4070b0, size 4
allocate 0x4080b0, size 4
allocate 0x4090b0, size 4
allocate 0x40a0b0, size 4
allocate 0x40b0b0, size 4
allocate 0x40c0b0, size 4
allocate 0x40d0b0, size 4
allocate 0x40e0b0, size 4
allocate 0x40f0b0, size 4
allocate 0x4100b0, size 4
allocate 0x4110b0, size 4
allocate 0x4120b0, size 4
allocate 0x4130b0, size 4
exception 1: std::bad_alloc
--2-- free 0x4040b0, size 4
--2-- free 0x4050b0, size 4
--2-- free 0x4060b0, size 4
--2-- free 0x4070b0, size 4
--2-- free 0x4080b0, size 4
--2-- free 0x4090b0, size 4
--2-- free 0x40a0b0, size 4
--2-- free 0x40b0b0, size 4
--2-- free 0x40c0b0, size 4
--2-- free 0x40d0b0, size 4
--2-- free 0x40e0b0, size 4
--2-- free 0x40f0b0, size 4
--2-- free 0x4100b0, size 4
--2-- free 0x4110b0, size 4
--2-- free 0x4120b0, size 4
--2-- free 0x4130b0, size 4
try to delete nullptr
try to delete nullptr
try to delete nullptr
try to delete nullptr
try to delete nullptr
try to delete nullptr
try to delete nullptr
try to delete nullptr
exception 2: std::bad_alloc

观察到一个有趣的事实:delete nullptr不会被执行!请在C++中使用nullptr

Delete operation performs a null pointer check anyway. So performing another null pointer check is an unnecessary operation and for me personally it makes the code ugly. But of course it is a good practice to set the pointer to nullptr after deleting as the pointer location is invalid after deletion.

代码中重载了两个delete,一开始只有delete(void* P)g++编译有warning,但是运行一切OK。为了抑制告警,加上了delete(void* p, size_t size),最后发现,所有的delete,都只调用后来加的那个接口。但是,这两个重载的delete,一个都不能删除,删除任意一个,都会有warning,提示你需要定义.......

将new和delete封装都管理资源的type中去,通过使用这些type的object来管理资源,不要出现naked new and delete操作符......将new和detele隐藏起来...

本文链接:https://cs.pynote.net/sf/c/cpp/202209021/

-- EOF --

-- MORE --