这个使用placement new[]的方法有什么问题?do

7
考虑下面的程序。它已从一个复杂的案例简化而来。除非我删除Obj类中的虚析构函数,否则它无法删除之前分配的内存。我不明白为什么程序输出的两个地址会有所不同,只有在存在虚析构函数时才会出现这种情况。
// GCC 4.4
#include <iostream>

using namespace std;

class Arena {
public:
    void* alloc(size_t s) {
        char* p = new char[s];
        cout << "Allocated memory address starts at: " << (void*)p << '\n';
        return p;
    }

    void free(void* p) {
        cout << "The memory to be deallocated starts at: " << p << '\n';
        delete [] static_cast<char*> (p); // the program fails here
    }
};

struct Obj {
    void* operator new[](size_t s, Arena& a) {
        return a.alloc(s);
    }

    virtual ~Obj() {} // if I remove this everything works as expected

    void destroy(size_t n, Arena* a) {
        for (size_t i = 0; i < n; i++)
            this[n - i - 1].~Obj();
        if (a)
            a->free(this);
    }
};


int main(int argc, char** argv) {
    Arena a;

    Obj* p = new(a) Obj[5]();
    p->destroy(5, &a);

    return 0;
}

这是我的实现中当虚拟析构函数存在时程序的输出:

分配的内存地址开始于:0x8895008 要释放的内存开始于:0x889500c

运行失败(退出值为1)

请不要问这个程序应该做什么。正如我所说,它来自一个更复杂的案例,其中Arena是各种类型内存的接口。在这个示例中,内存只是从堆中分配和释放的。

请注意,如果新表达式引发异常,则需要匹配的放置数组删除。 - Alexandre C.
2个回答

5

this不是char* p = new char[s];这一行返回的指针。你可以看到,这里的大小s比5个Obj实例要大。差异(应该是sizeof(std::size_t))在于附加内存,其中包含数组长度5,即在this所包含的地址之前。

好的,规范已经明确:

http://sourcery.mentor.com/public/cxx-abi/abi.html#array-cookies

2.7 数组操作符new Cookie

当使用operator new创建新的数组时,通常会存储一个Cookie以记住已分配的长度(数组元素数量),以便可以正确地释放内存。

具体来说:

如果数组元素类型T具有平凡的析构函数(12.4 [class.dtor]),并且通常的(数组)释放函数(3.7.3.2 [basic.stc.dynamic.deallocation])函数没有两个参数,则不需要Cookie。

因此,析构函数的虚拟性无关紧要,重要的是析构函数是非平凡的。您可以通过删除析构函数前面的关键字virtual并观察程序崩溃来轻松检查它是否平凡。


是的,我并不真的关心大小。我关心的是为什么这与指针alloc()给定的放置对象的位置不匹配。 - Martin
@Martin,因为alloc()已经为数组的长度和元素分配了额外的空间。如果没有虚析构函数,我现在不太清楚为什么它不会存储数组长度,必须检查C++ ABI规范。 - chill
这是什么规范?我的C++规范中没有提到cookies。 - CB Bailey
@CharlesBailey,GCC C++ ABI规范,http://sourcery.mentor.com/public/cxx-abi/abi.html - chill
@chill:谢谢。也许你应该在你的回答中加上引用的参考资料。 - CB Bailey

0

根据chills的回答,如果你想让它更"安全":

#include <type_traits>

a->free(this - (std::has_trivial_destructor<Obj>::value ? 1 : 0));

1
不,不完全是。也许可以使用 a->free ((std::size_t *)this - (std::has_trivial_destructor<Obj>::value ? 1 : 0));,但严格来说这是未定义的行为,我相信它在某些晦涩的情况和/或不同的编译器中会出问题 ;) - chill

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