在从放置 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-expression 和 delete-expression我>。不幸的是,根据 James 引用的当前措辞,这段代码是这样做的。
我在问题代码 sn-p 中添加了您的报价明显非法的调用,以进行比较。
@Ben 不,它没有,因为您传递了一个指向 delete
的指针,而 delete
又用该指针(或用一个由常数因子调整的指针)调用 operator delete
,而这又反过来将导致未定义的行为。所以你不玩规则。以上是关于在从放置 new 获得的指针上使用 operator delete 的合法性的主要内容,如果未能解决你的问题,请参考以下文章
我会在从 v3 迁移到最新 v7 的迁移 lucene 索引中获得性能改进吗
是否允许显式调用析构函数,然后在具有固定生命周期的变量上放置 new?
当我放置 Select new 时,Linq 查询在 EntityDataSource1_QueryCreated 事件上给出异常;