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 是各种类型内存的接口。在这个例子中,内存只是从堆中分配和释放。

4

2 回答 2

5

this不是newat 行返回的指针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 Cookies

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

具体来说:

如果数组元素类型 T 有一个普通的析构函数 (12.4 [class.dtor]) 并且通常的 (array) 释放函数 (3.7.3.2 [basic.stc.dynamic.deallocation]) 函数不接受两个参数,则不需要 cookie .

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

于 2011-11-24T20:55:12.857 回答
0

根据寒战的回答,如果你想让它“安全”:

#include <type_traits>

a->free(this - (std::has_trivial_destructor<Obj>::value ? 1 : 0));
于 2011-11-24T21:28:08.023 回答