在从放置 new 获得的指针上使用 operator delete 的合法性

Posted

技术标签:

【中文标题】在从放置 new 获得的指针上使用 operator delete 的合法性【英文标题】:Legality of using operator delete on a pointer obtained from placement new 【发布时间】:2011-05-24 00:35:30 【问题描述】:

我很确定这段代码应该是非法的,因为它显然不起作用,但它似乎是 C++0x FCD 允许的。

class X  /* ... */;
void* raw = malloc(sizeof (X));
X* p = new (raw) X(); // according to the standard, the RHS is a placement-new expression
::operator delete(p); // definitely wrong, per litb's answer
delete p; // legal?  I hope not

也许你们中的一位语言律师可以解释标准是如何禁止这样做的。

还有数组形式:

class X  /* ... */;
void* raw = malloc(sizeof (X));
X* p = new (raw) X[1]; // according to the standard, the RHS is a placement-new expression
::operator delete[](p); // definitely wrong, per litb's answer
delete [] p; // legal?  I hope not

This is the closest question我找到了。

编辑:我只是不相信标准语言限制函数void ::operator delete(void*) 的参数以任何有意义的方式应用于删除表达式delete 的操作数。充其量,两者之间的联系是非常脆弱的,并且许多表达式允许作为delete 的操作数,这些表达式对于传递给void ::operator delete(void*) 是无效的。例如:

struct A

  virtual ~A() 
;

struct B1 : virtual A ;

struct B2 : virtual A ;

struct B3 : virtual A ;

struct D : virtual B1, virtual B2, virtual B3 ;

struct E : virtual B3, virtual D ;

int main( void )

  B3* p = new E();
  void* raw = malloc(sizeof (D));
  B3* p2 = new (raw) D();

  ::operator delete(p); // definitely UB
  delete p; // definitely legal

  ::operator delete(p2); // definitely UB
  delete p2; // ???

  return 0;

我希望这表明是否可以将指针传递给void operator delete(void*) 与是否可以将同一指针用作delete 的操作数无关。

【问题讨论】:

仅供参考:FCD (N3092) 不再是最新草案。最新的草案是 N3225。我一直在更新c++-0x tag wiki page,并提供指向最新 PDF 草案的链接。 请注意,涵盖此内容的 5.3.5/2 已在最新草案中进行了修改。它现在说指针可能是“指向由先前的 new-expression 创建的非数组对象的指针”,并且 new-expression 确实包括放置 new表达式。我不认为这是故意的。 @James:非常感谢新草稿。而 5.3.5 正是我认为应该禁止的部分,但没有。请您看看我的回答(我正准备从新草案中提取任何更改的语言),如果您认为它与这个问题有任何关系,请告诉我? @James:关于 C++0x 的精彩页面,感谢最新草稿,我无权编辑它(没有青铜 C++0x 徽章:p),你认为您可以添加 Clang C++0x 状态。实施才真正开始(他们直到现在都专注于 C++03 合规性),但已经实施了一些功能。这是链接:clang.llvm.org/cxx_status.html @James,您能否将您的评论放入答案中以便我接受? 【参考方案1】:

编译器并不真正关心p 来自放置new 调用,因此它不会阻止您在对象上发出delete。这样,您的示例可以被认为是“合法的”。

但这不起作用,因为不能显式调用“placement delete”运算符。它们只有在构造函数抛出时才会被隐式调用,因此析构函数可以运行。

【讨论】:

【参考方案2】:

如果

    new/delete 是按照malloc/free 和 来实现的 位置new 实际上使用与标准new 相同的机制来跟踪与分配关联的析构函数,因此对delete 的调用可以找到正确的析构函数。

但它不可能是便携的或安全的。

【讨论】:

这绝对不安全,因为调用释放函数需要删除表达式,传递一个不是来自匹配分配函数的指针。【参考方案3】:

我在标准的库部分中发现了这一点,这与位置 (IMO) 的反直觉差不多:

C++0x FCD(和 n3225 草案)第 18.6.1.3 节,[new.delete.placement]:

这些函数是保留的,一个 C++ 程序可能没有定义函数 取代标准中的版本 C++ 库 (17.6.3)。规定 (3.7.4) 不适用于这些 运营商预留安置形式 new 和 operator 删除。

void*  operator  new(std::size_t  size,  void*  ptr)  throw();
void*  operator  new[](std::size_t  size,  void*  ptr)  throw();
void  operator  delete(void*  ptr,  void*)  throw();
void  operator  delete[](void*  ptr,  void*)  throw();

不过,定义要传递给delete 的合法表达式的部分是 5.3.5,而不是 3.7.4。

【讨论】:

【参考方案4】:

[basic.stc.dynamic.deallocation]p3 中的标准规则

否则,提供给标准库中operator delete(void*) 的值应是先前调用标准库中的operator new(size_t)operator new(size_t, const std::nothrow_t&) 返回的值之一,以及提供给operator delete[](void*) 的值标准库应是先前在标准库中调用 operator new[](size_t)operator new[](size_t, const std::nothrow_t&) 返回的值之一。

您的delete 调用将调用库的operator delete(void*),除非您已将其覆盖。既然你什么都没说,我会假设你没有。

上面的“应该”真的应该是“如果不是行为是未定义的”这样的东西,所以它不会被误认为是一个可诊断的规则,它不是 [lib.res.on.arguments]p1。这已被 n3225 更正,因此不会再弄错了。

【讨论】:

这当然适用于使用函数调用语法直接调用释放函数。但我不认为它立即适用于operator delete 的调用,因为我用作operator delete 操作数的指针不一定是最终传递给释放函数的指针。好吧,仔细阅读 5.3.4 [expr.new],标量 new 也是如此。因此,请考虑数组放置 new,然后是数组删除。 @Ben 我不明白你的评论。 编译器应该负责遵循释放函数的规则,只要我遵循 new-expressiondelete-expression我>。不幸的是,根据 James 引用的当前措辞,这段代码是这样做的。 我在问题代码 sn-p 中添加了您的报价明显非法的调用,以进行比较。 @Ben 不,它没有,因为您传递了一个指向 delete 的指针,而 delete 又用该指针(或用一个由常数因子调整的指针)调用 operator delete,而这又反过来将导致未定义的行为。所以你不玩规则。

以上是关于在从放置 new 获得的指针上使用 operator delete 的合法性的主要内容,如果未能解决你的问题,请参考以下文章

C++:指针和new,delete详解

如何获取光标:嵌入对象上的指针?

我会在从 v3 迁移到最新 v7 的迁移 lucene 索引中获得性能改进吗

是否允许显式调用析构函数,然后在具有固定生命周期的变量上放置 new?

怎么使用new操作符创建动态数组?

当我放置 Select new 时,Linq 查询在 EntityDataSource1_QueryCreated 事件上给出异常;