【问题标题】:Legality of using operator delete on a pointer obtained from placement new在从放置 new 获得的指针上使用 operator delete 的合法性
【发布时间】: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,您能否将您的评论放入答案中以便我接受?

标签: c++ language-lawyer delete-operator placement-new


【解决方案1】:

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

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

【讨论】:

    【解决方案2】:

    如果

    1. new/delete 是按照malloc/free
    2. 来实现的
    3. 位置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,而这又反过来将导致未定义的行为。所以你不玩规则。
      猜你喜欢
      • 2013-05-19
      • 1970-01-01
      • 2012-01-03
      • 1970-01-01
      • 2011-04-18
      • 1970-01-01
      • 2016-03-04
      • 1970-01-01
      • 2020-02-05
      相关资源
      最近更新 更多