我偶然发现了以下代码片段:
#include <iostream>
#include <string>
using namespace std;
class First
{
string *s;
public:
First() { s = new string("Text");}
~First() { delete s;}
void Print(){ cout<<*s;}
};
int main()
{
First FirstObject;
FirstObject.Print();
FirstObject.~First();
}
文字说,此代码段应导致运行时错误。现在,我对此不太确定,因此尝试编译并运行它。有效。奇怪的是,尽管所涉及的数据很简单,但该程序在打印“文本”之后却卡住了,并且仅在完成一秒钟后就卡住了。
我添加了一个要打印到析构函数的字符串,因为我不确定像这样显式调用析构函数是否合法。程序将字符串打印两次。因此,我的猜测是析构函数被调用两次,因为普通程序终止不知道显式调用,并试图再次销毁该对象。
一个简单的搜索确认,在自动对象上显式调用析构函数是危险的,因为第二次调用(当对象超出范围时)具有不确定的行为。因此,我对自己的编译器(VS 2017)或该特定程序感到幸运。
有关运行时错误的文字是否完全错误?还是发生运行时错误真的很常见?还是我的编译器针对这种情况实施了某种转发机制?
答案 0 :(得分:34)
简单的搜索确认,在自动对象上显式调用析构函数是危险的,因为第二次调用(对象超出范围时)具有不确定的行为。
是的。如果使用自动存储明确销毁对象,则会调用未定义的行为。 Learn more about it。
所以我很庆幸我的编译器(VS 2017)或这个特定程序。
我会说你倒霉。 UB可能发生的最好情况(对您而言,对于编码人员而言)是第一次运行时发生崩溃。如果运行正常,则可能在2038年1月19日投入生产。
有关运行时错误的文字是否完全错误?还是发生运行时错误真的很常见?还是我的编译器针对这种情况实施了某种转发机制?
是的,文本有点不对。 未定义的行为是未定义的。运行时错误只是许多可能性(包括鼻恶魔)中的一种。
深入了解未定义的行为:What is undefined behavor?
答案 1 :(得分:15)
不,这仅仅是C ++标准草案[class.dtor]p16中未定义的行为:
一旦为一个对象调用了析构函数,该对象就不再存在;如果为生存期已结束的对象([basic.life])调用析构函数,则该行为未定义。 [示例:如果显式调用了自动对象的析构函数,并且随后以通常会调用对象的隐式销毁的方式保留该块,则该行为是不确定的。 示例
我们可以从defintion of undefined behavior中看到:
本文档不要求其行为的行为
您可能对结果没有期望。对于作者来说,在特定编译器上使用特定机器上的特定选项时,可能会表现出这种方式,但是我们不能指望它是可移植且可靠的结果。虽然在某些情况下implementation does try to obtain a specific result只是可接受的未定义行为的另一种形式。
另外,[class.dtor]p15在我上面引用的规范部分中提供了更多的上下文:
[注:很少需要析构函数的显式调用。 这种调用的一种用法是使用new-expression放置在特定地址的对象。 显式放置和破坏对象的这种使用对于应付专用硬件资源和编写内存管理工具可能是必需的。 例如,
void* operator new(std::size_t, void* p) { return p; } struct X { X(int); ~X(); }; void f(X* p); void g() { // rare, specialized use: char* buf = new char[sizeof(X)]; X* p = new(buf) X(222); // use buf[] and initialize f(p); p->X::~X(); // cleanup }
—注释]
答案 2 :(得分:9)
有关运行时错误的文字是否完全错误?
错了。
或者出现运行时错误真的很常见吗?还是我的编译器针对这种情况实施了某种转发机制?
您不知道,这就是您的代码调用Undefined Behavior时发生的情况;您不知道执行时会发生什么。
在您的情况下,您很幸运( * )并且有效,而对我而言,它导致了error(双重免费)。
*因为如果收到错误,您将开始调试,否则,例如在一个大型项目中,您可能会错过它...