使用重载的`new[]`分配内存并委托给`malloc`,释放内存时是否安全?

10

我的问题并非“Is it safe to `free()` memory allocated by `new`?”的重复。

我正在为POD编写一个玩具垃圾收集器,在其中定义了自己的自定义operator new/new[]operator delete/delete[]。以下是代码:

#include <iostream>
#include <map>

std::map<void*, std::size_t> memory; // globally allocated memory map
struct collect_t {} collect; // tag for placement new

void* operator new(std::size_t size, const collect_t&)
{
    void* addr = malloc(size);
    memory[addr] = size;
    return addr;
}

void* operator new[](std::size_t size, const collect_t&) 
{
    return operator new(size, collect);
}

void operator delete(void *p, const collect_t&) noexcept
{
    memory.erase(p); // should call ::operator delete, no recursion
    free(p);
}

void operator delete[](void *p, const collect_t&) noexcept
{
    operator delete(p, collect);
}

void display_memory()
{
    std::cout << "Allocated heap memory: " << std::endl;
    for (auto && elem : memory)
    {
        std::cout << "\tADDR: " << elem.first << " "
                  << "SIZE: "  << elem.second << std::endl;
    }
}

void clear()
{
    for (auto && elem : memory)
        free(elem.first); // is this safe for arrays?
    memory.clear();
}

int main()
{
    // use the garbage collector
    char *c = new(collect) char; 
    int *p = new(collect) int[1024]; // true size: sizeof(int)*1024 + y (unknown overhead)

    display_memory();
    clear();
    display_memory();
}
这个想法很简单:我用自定义的new来分配内存并将所有已分配的跟踪地址存储在std::map中,并确保在我的clear()函数中清除所有内存。我为我的newdelete使用标签(不重载全局函数),以便std::map的分配器可以调用全局函数而不会导致递归调用。
我的问题是:在我的clear()函数中,我在这一行中释放内存
for (auto && elem : memory)
    free(elem.first); // is this safe for arrays?

对于数组,例如int *p = new(collect) int[1024];,这种方法安全吗?我认为是安全的,因为void* operator new[](std::size_t size, const collect_t&) 调用了 operator new(size, collect);,而后者又调用了malloc。尽管如此,我并不100%确定,这种方法是否存在错误的可能性?


1
只要确保每个人都使用您的操作符,包括单独构建的库,那么是的。往往很难保证。 - Hans Passant
1
如果正确操作,这是安全的:您的放置new操作正在添加附加标题信息(大小)。因此,您需要通过aligned(sizeof(header))增加分配的内存量,并返回头部后的(对齐的)指针。在取消分配时,必须首先减去该大小。此外,需要一些纪律使用新的/删除对。 - user2249683
1
请查看这个问题 - jxh
1
@DieterLücking,您的担忧似乎不适用于此。这不是将placement new放入void*中,而是自定义placement new。根据我对标准片段的阅读,传递的size_t将包括所需的任何开销?通常对于placement new的关注点是在调用new[]之前确定目标缓冲区大小,这可能会请求比缓冲区更多的内存。但是,我不确定如何处理对齐方式。简而言之:传递给new[]的大小是请求的内存大小,而不是数组的大小。 - Yakk - Adam Nevraumont
1
@DieterLücking n4296 [expr.new] 4.3.4/11和/12和/13和/14--请提供证据,证明我的解释是错误的,或者标准已经发生了根本性的变化?这条评论已被编辑。示例中有明确的声明,数组开销将被传递,并且其他文本也暗示了这一点。如果传递给new[]size_t不包括该开销,我无法想象如何使new[]有用。因此,我对你的说法持怀疑态度。我是否误解了你的说法?我错了吗?还是你错了? - Yakk - Adam Nevraumont
显示剩余14条评论
2个回答

2

我认为,为了使内存被放入你的memory容器中,它必须使用你的自定义分配器进行分配,该分配器始终调用malloc。因此,我认为你的代码调用free应该是可以的。

显然,如果有人在内存映射中随意插入地址,那么你就会遇到各种未定义的行为。


1
假设使用您的垃圾回收器的对象从未实现析构函数,并且对于这些对象可能包含的任何成员也是如此,那么代码在某种意义上是“安全”的,因为对free()的直接调用只是绕过编译器为内联delete调用执行相同操作所需的工作。
然而,“该代码实际上并不安全”。
如果您曾经更改了垃圾回收器的工作方式或new函数的工作方式,则必须查找所有直接调用free()以防止任何问题。如果代码曾经被剪切和粘贴或以其他方式在垃圾回收器之外的上下文中重用,则会面临类似的问题。
最好的做法是始终将newdelete匹配,将mallocfree匹配。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接