【问题标题】:Confusion about deleting objects created via placement new关于删除通过放置新创建的对象的困惑
【发布时间】:2015-06-24 21:53:32
【问题描述】:

我不确定以下代码中发生了什么:

#include <iostream>

struct Foo
{
    double dummy{42};
    static void* operator new(std::size_t size, void* p)
    {
        std::cout << R"(Calling placement "operator new" for size )"
                  << size << '\n';
        return ::operator new(size, p);
    }

    Foo()
    {
        std::cout << "Foo::Foo()" << std::endl;
    }
    ~Foo()
    {
        std::cout << "~Foo::Foo()" << std::endl;
    }
};

int main()
{
    void* buf_heap = new char[128] {};
    Foo* pFoo_heap = new(buf_heap) Foo; // placement allocation

    // why does this line call the correct operator delete?
    delete pFoo_heap;

    // the line above seems to be equivalent to:
    // pFoo_heap->~Foo();
    // ::operator delete(buf_heap);
}

我知道每当在构造对象时使用放置new 时,都应手动调用析构函数,然后调用释放内存(通常通过::operator delete::operator delete[]free,具体取决于关于放置new 是如何实现的),参见例如How to delete object constructed via placement new operator?

但是,在我上面的代码中,我创建了一个 Foo 对象,我将它放在堆分配的内存中。然后,当我调用delete pFoo_heap 时,会自动调用析构函数(我理解),但是似乎内存也被释放了(在这种情况下为buf_heap)。也就是说,如果我尝试执行::operator delete[](buf_heap);,我会遇到段错误。

所以基本上是一行

delete pFoo_heap;

似乎等价于

pFoo_heap->~Foo(); 
::operator delete[](buf_heap);

确实是这样(或者只是UB)?为什么buf_heap中分配的内存被取消分配了?或者,换句话说,delete pFoo_heap; 是否知道内存来自哪里,即使是通过放置new 分配的?

【问题讨论】:

  • 猜猜:你很幸运,因为你放置的对象和堆块从同一个地址开始。尝试在buf_heap 中间分配pFoo_heap 看看会发生什么。
  • @RichardCritten 很棒的评论,我没想到这一点。所以这是UB的一个很好的例子......

标签: c++ memory-management placement-new


【解决方案1】:

为什么buf_heap中分配的内存被取消分配了?

因为您正在取消分配它。请注意buf_heap == pFoo_heap,因此当您执行delete pFoo_heap 时,它的工作方式恰好与您开始执行new Foo 时一样——您要删除的指针指向先前使用new 分配的内存。所以它......“有效”......

【讨论】:

  • 是的,但确实非常糟糕,因为如果我这样做new(buf_heap + 42) Foo,那么所有的赌注都会被取消。
【解决方案2】:

你问:

// why does this line call the correct operator delete?
delete pFoo_heap;

// the line above seems to be equivalent to:
// pFoo_heap->~Foo();
// ::operator delete(buf_heap);

那不是真的。您的代码会受到未定义行为的影响。来自 C++ 标准:

5.3.5 删除

2 如果操作数具有类类型,则通过调用上述转换函数将操作数转换为指针类型,并在本节的其余部分中使用转换后的操作数代替原始操作数。在第一种选择(delete object)中,delete 的操作数的值可能是一个空指针值,一个指向由前一个 new-expression 创建的非数组对象的指针,或指向表示此类对象的基类的子对象(1.8)的指针(第 10 条)。如果不是,则行为未定义。

在您的情况下,pFoo_heap 不是由 new-expression 创建的,buf_heap 是由 new-expression 创建的。你需要使用:

pFoo_heap->~Foo();
delete [] buf_heap;

对于一个表现良好的程序。

【讨论】:

  • 这确实是正确的做事方式。
  • 感谢标准参考。我知道我确实应该先调用 dtor,然后最终释放内存,但不确定为什么我的 sn-p 似乎工作。但是行 "a pointer to an pointer to a non-array object created by a previous new-expression" 似乎也考虑了放置 new,不是吗?因为它仍然是一个新表达式
  • 在 5.3.4 中,placement new 似乎是一个new-expression,所以imo pFoo_heap 是由一个new-expression创建的>。我是不是误会了什么?
  • @vsoftco,第 5.3.4 节中的布置new 解决了除我们在此讨论的那一种之外的所有形式的布置new。我们在这里讨论的位置new 在第 18.6.1.3 节中更清楚地提到了。该部分表示有意不执行其他操作。 此外,en.cppreference.com/w/cpp/language/new 有一小部分讨论了我们正在讨论的位置new
猜你喜欢
  • 2016-04-29
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-05-04
  • 2011-10-07
  • 2011-07-20
  • 1970-01-01
相关资源
最近更新 更多