sha*_*oth 16 c++ memory destructor memory-management undefined-behavior
正如本回答中提到的,第二次简单地调用析构函数已经是未定义的行为12.4/14(3.8).
例如:
class Class {
public:
~Class() {}
};
// somewhere in code:
{
Class* object = new Class();
object->~Class();
delete object; // UB because at this point the destructor call is attempted again
}
Run Code Online (Sandbox Code Playgroud)
在这个例子中,类的设计方式可以多次调用析构函数 - 不会发生双删除之类的事情.内存仍在delete调用的位置分配- 第一个析构函数调用不调用::operator delete()释放内存.
例如,在Visual C++ 9中,上面的代码看起来很有效.甚至UB的C++定义也没有直接禁止符合UB条件的东西.因此,对于上面的代码来说,需要打破一些实现和/或平台细节.
为什么上面的代码会在什么条件下中断呢?
Seb*_*ian 13
我认为你的问题针对的是标准背后的基本原理.反过来考虑一下:
那又为什么这不会导致不确定的行为呢?
在标准中制定的原因很可能是其他一切都要复杂得多:它必须定义何时可以完全双重删除(或反过来) - 即使用简单的析构函数或使用析构函数的副作用可以丢弃.
另一方面,这种行为没有任何好处.实际上,你无法从中获利,因为你无法一般地知道类析构函数是否符合上述标准.没有通用代码可以依赖于此.以这种方式引入错误将非常容易.最后,它有什么帮助?它只是可以编写不跟踪其对象生命周期的草率代码 - 换句话说,指定代码不足.标准为什么要支持这个?
现有的编译器/运行时是否会破坏您的特定代码?可能不会 - 除非他们有特殊的运行时检查来防止非法访问(以防止看起来像恶意代码,或只是泄漏保护).
调用析构函数后,该对象不再存在.
因此,如果再次调用它,则会在不存在的对象上调用方法.
为什么这会被定义为行为?出于调试/安全性/某种原因,编译器可以选择将已被破坏的对象的内存清零,或者将其内存与另一个对象作为优化或其他任何东西进行回收.实施可以随心所欲.再次调用析构函数本质上是在任意原始内存上调用一个方法 - 一个坏主意(tm).
析构函数不是常规函数。调用一个函数并不是调用一个函数,而是调用多个函数。这是析构函数的魔力。虽然您提供了一个简单的析构函数,其唯一目的是使其难以显示它可能如何破坏,但您未能演示被调用的其他函数的作用。标准也没有。正是在这些功能中,事情可能会崩溃。
举一个简单的例子,假设编译器插入代码来跟踪对象的生命周期以进行调试。构造函数[这也是一个神奇的函数,可以完成您没有要求它做的各种事情]在某个地方存储一些数据,表示“我在这里”。在调用析构函数之前,它会更改该数据以表示“我开始了”。调用析构函数后,它会删除用于查找该数据的信息。因此,下次调用析构函数时,您最终会遇到访问冲突。
您可能还可以提供涉及虚拟表的示例,但您的示例代码不包含任何虚拟函数,因此这将是作弊。