【问题标题】:Would combining raw operator new, placement new and standard delete be legal?将原始运算符 new、placement new 和标准删除结合起来是否合法?
【发布时间】:2011-10-02 00:21:38
【问题描述】:

伙计们!出于好奇——下面的代码可能不合法,不是吗?

T *p = ::operator new(sizeof(T)); // allocate memory for a T
new (p) T; // construct a T into the allocated memory
delete p; //delete the object using the standard delete operator

【问题讨论】:

  • 几乎与@Fredoverflow 的recent question 完全相同,尽管有足够的差异可能值得讨论。从技术上讲,它是不允许的(因为::operator new 返回void *,所以你需要对分配进行强制转换),但是在固定的情况下,这是一个很好的问题(我不确定它是否像干的那样)正如 DeadMG 的回答所暗示的那样)。
  • 有什么意义?如果你想要T*new T;

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


【解决方案1】:

没有。你只能delete 你从new 得到的东西——没有例外。

【讨论】:

  • delete 必须与 new 配对,但在这种情况下是配对的。新的展示位置仍然是一种新的表达方式。
【解决方案2】:

至少在一种情况下它显然是未定义的:如果你为T 重载了operator newoperator delete,那么这将尝试使用::operator new 分配内存,但使用T::operator delete 删除它.除非您的 T::operator delete 纯粹是 ::operator delete 的包装,否则会导致问题。

除此之外,我认为它可能已定义。该标准非常具体地说明new expression 使用分配函数(§5.3.4/10)分配其内存,只要您没有提供T::operator new,它将是::operator new

然后,您的放置新表达式会初始化对象,就像在 §5.3.4/15 的第一个要点中描述的新表达式一样。

然后我们来到破坏的一边。根据 $5.3.5/1 :“delete-expression 运算符破坏了由 new-expression 创建的最派生对象 (1.8) 或数组。”这要求您使用新的表达式来创建对象——您已经拥有了。您使用了新的展示位置,这是新表达式的一种可能性,并在 §5.3.4/1 中指定。

适用的下一个要求似乎是:“操作数应具有指针类型,或具有单个转换函数 (12.3.2) 到指针类型的类类型。”同样,您的表达方式也符合这一要求。

我将引用更多要求,无需进一步评论,只是您的代码似乎满足所有要求(某些限制了删除表达式的实现,而不是您可以在一个中使用的指针):

  • (§5.3.5/2):“在第一种选择(删除对象)中,删除操作数的值应为指向非数组对象的指针或指向子对象的指针(1.8) 表示此类对象的基类(第 10 条)。如果不是,则行为未定义。”

  • (§5.3.5/3):“在第一种选择(删除对象)中,如果操作数的静态类型与其动态类型不同,则静态类型应为操作数的基类动态类型和静态类型应该有一个虚析构函数或者行为未定义。

  • (§5.3.5/4):“在第一种选择(删除对象)中,如果操作数的静态类型与其动态类型不同,则静态类型应为操作数的基类动态类型和静态类型应具有虚拟析构函数或行为未定义。”

  • (第 5.3.5/6 节):“delete 表达式将为要删除的对象或数组元素调用析构函数(如果有)。”

  • (§5.3.5/7):删除表达式将调用释放函数 (3.7.3.2)。

关于::operator newT::operator new 的初始警告,我认为您在delete expression 中使用的指针满足所有要求,因此应该定义行为。

说了这么多,我当然希望这纯粹是学术兴趣 - 尽管在我看来代码确实具有定义的行为,但即使在最好的情况下,这也是一个糟糕的想法。 p>

【讨论】:

  • 你只提到了“删除表达式”。 ::operator delete 呢?由于构造涉及手动分配,然后是放置新,所以更对称的销毁序列不是p->~T();(销毁)、delete (p) p;(无操作)、::operator delete(p);(释放)吗?
  • 是的,为了与他的分配对称,他当然可以这样做。问题是删除表达式是否需要与之等效。除了delete (p) p;,我认为是。
  • 不一定是这样,不是吗?您需要以与获取相同的方式删除,因此其他任何内容都只是 UB,例如删除分配的指针。
  • @Kerrek:我能找到的所有要求都在上面列出。我看不出他的代码在哪里违反了其中任何一个。因此,行为似乎已定义。删除 malloced 指针确实违反了它们,所以它似乎根本不一样。
  • 我无法看透这些子句 :-( 但请查看Wikipedia entry:“无法使用删除表达式销毁对象 [用placement-new 构造],这就是破坏一个通过指针放置 new 表达式构造的对象。”你对此有何看法?
【解决方案3】:

脱离 DeadMG 的正确断言,对您的代码稍作改动没有问题:

unsigned char* addr = new unsigned char[sizeof(MySimpleStructure)];
MySimpleStructure* p = new (addr) MySimpleStructure;
delete [] addr;

由于我们是deleteing addr,这是由new 返回的,因此这合法没有问题。当然,addr 被删除后,p 不应该被访问(当时它是一个悬空指针)。另请注意,MySimpleStructure 分配给 p 的新位置不会调用其析构函数。

【讨论】:

  • 您错过了 DeadMG 答案中的“无例外”部分。
  • @BoP:这是个例外吗?
  • 同意。 “你只能删除你从新得到的东西”。既然您投了反对票,请解释一下答案如何违反了该规则。事实上,如果我发布的内容不合法,请展示一个使用placement new 和动态分配内存的示例,该内存稍后会被释放。
  • @Chad - 你自己说没有调用析构函数。这是怎么回事?
  • addr 的“析构函数”被调用,给定上面的代码 sn-p,这就是所有可以预期的。新的放置经常以这种方式使用,例如一个字节对齐的结构,紧随其后的是一些任意长度的二进制数据。 MySimpleStructure::~MySimpleStructure 未被调用这一事实不是问题,只要编写此代码的编码人员理解这一事实。
猜你喜欢
  • 2012-10-15
  • 1970-01-01
  • 1970-01-01
  • 2021-08-31
  • 1970-01-01
  • 1970-01-01
  • 2021-08-22
  • 1970-01-01
  • 2023-03-23
相关资源
最近更新 更多