【问题标题】:Why is the destructor ignored in this code?为什么在这段代码中忽略了析构函数?
【发布时间】:2010-10-03 04:33:00
【问题描述】:

以下代码演示了我在 Turbo C++ Explorer 项目中遇到的一个奇怪问题。 D::D() 中的三个堆栈对象之一在超出范围后不会被销毁。

只有在发布模式下编译时才会发生这种情况,auto_ptrs a_ 和 b_ 的类型不同,并且抛出的异常不继承自 std::exception。它似乎在 VC++ 2005 和 C++ Builder 2009 中运行良好。我确实安装了 BDS2006 更新 2、修补程序汇总和修补程序 12。

是我的代码还是编译器?你知道修复吗?不能在 VCL 项目中可靠地使用 auto_ptr 会很不方便。


#include <memory>
#include <stdexcept>
#include <iostream>

typedef std::exception my_error; // will work fine if replaced with line below
//class my_error : public std::exception {};

class A {};
class B {};

class C
{
public:
    C(int id) : id_(id) { std::cout << "C::C() " << id_ << std::endl; };
    ~C() { std::cout << "C::~C() " << id_ << std::endl; };
private:
    int id_;
};

class D
{
public:
    D()
    {
        C c1(1);
        C c2(2);
        C c3(3);

        throw my_error();
    };

private:
    std::auto_ptr<A> a_;
    std::auto_ptr<B> b_; // will work fine if replaced with line below
//  std::auto_ptr<A> b_;
//  std::auto_ptr<C> c_; // see expected output
};

#pragma argsused
int main(int argc, char* argv[])
{
    try
    {
        D d;
    }
    catch (...)
    {
        std::cout << "caught exception" << std::endl;
    }

    return 0;
}


预期:

C::C() 1 C::C() 2 C::C() 3 C::~C() 3 C::~C() 2 C::~C() 1 捕捉到异常


得到:

C::C() 1 C::C() 2 C::C() 3 C::~C() 2 C::~C() 1 捕捉到异常


得到(没有注释行'// std::auto_ptr&lt;C&gt; c_;'):

C::C() 1 C::C() 2 C::C() 3 C::~C() 1 捕捉到异常


编辑:进行了建议的更改

编辑 2:
我刚刚用 C++ Builder 2007 (11.0.2902.10471) 对其进行了测试,它显示了同样的问题。只要我检查项目 -> 选项 -> C++ 编译器 -> 调试中的“调试信息”框,发布配置就会起作用。令我惊讶的是,启用“调试信息”后可执行文件变得更小(从 39.5 KB 降至 31.5 KB)。

编辑 3:
在 Turbo C++ Explorer (C++ Builder 2006) (10.0.2288.42451) 中,如果我取消选中项目 -> 选项 -> C++ 编译器 -> 调试中的“内联函数扩展 (-vi)”框,则发布配置有效。用以下代码替换第一行 (#include &lt;memory&gt;) 也可以正常工作。

#pragma option push -vi-
#include <memory>
#pragma option pop 

【问题讨论】:

  • 向 C 构造函数和析构函数添加一个额外的识别文本字符串来确定哪个没有被破坏可能会很有趣。我怀疑这是第一个,因为这是本地堆栈上的第一个。我的印象是一个对象留在堆栈上。
  • 另外,如果你实例化三个 C 对象会发生什么?只会破坏两个,还是只是破坏堆栈上的第一个?
  • 这很奇怪。就好像在抛出异常之前最后一个对象没有被放入堆栈......编译器是否允许您设置每个模块优化?如果是这样,请尝试有选择地关闭该代码区域的优化!

标签: c++ destructor c++builder


【解决方案1】:

这似乎是一个编译器错误。我刚刚在 VS2008SP1 中运行了相同的示例并得到了预期的输出。

【讨论】:

    【解决方案2】:

    不管它值多少钱,GCC 3.4.6 都做了预期的事情:

    $ g++ main.cpp
    
    $ a.out
    C::C()
    C::C()
    C::~C()
    C::~C()
    caught exception
    

    【讨论】:

      【解决方案3】:

      这是 C++Builder 2006 中的一个编译器错误。C++Builder 2009 修复了它;这是我为 BCC v6.1 得到的输出:

      C::C() 1
      C::C() 2
      C::C() 3
      C::~C() 3
      C::~C() 2
      C::~C() 1
      caught exception

      【讨论】:

        【解决方案4】:

        如果在对象构造函数中抛出异常,析构函数将不会运行。

        编译器无法知道构造函数是否足够完成以使析构函数正确运行。

        http://www.parashift.com/c++-faq-lite/exceptions.html#faq-17.4

        编辑:回复下面的评论... 在这种情况下,很可能是编译器错误将“不运行析构函数”规则与错误地不销毁堆栈上的对象混为一谈。

        【讨论】:

        • 在这个示例中,提问者担心的是构造函数中locals的析构函数,而不是D的析构函数
        • 好点。制作精良。在这种情况下,它很可能是编译器错误。我会相应地更新我的答案。
        • 另外,如果构造函数或析构函数有副作用,编译器是不允许优化掉局部变量的。看起来它就是这样做的。也许他应该尝试打印出两个对象的地址(它们的 this 指针)。如果他们碰巧在同一个地址,也不会感到惊讶。
        【解决方案5】:

        也许 cout 流没有被刷新?你可以试试 cerr 吗?还是直接在析构函数中下断点,检查是否命中?

        【讨论】:

        • 当然,如果输出流没有被刷新,'caught exception' 字符串也不会出现。
        • 另外,他说它只发生在发布模式下......很难在析构函数上放置断点:)
        • 让我们有第二个原因,为什么我不相信原因是 cout 的缓冲:他使用 std::endl,它实际上调用 .flush(),它会在换行后刷新流
        • 您可以使用 DebugBreak() 直接在代码中放置断点,甚至在调试器中以发布模式运行代码。如果你有一个带有符号的 .pdb,你就会有你的函数名称。
        • cerr 通常不被缓冲,它应该立即在屏幕上打印,相反 cout 可以被缓冲并等待 flush() 调用以在屏幕上打印。 (我有时会因为这种行为而被咬)
        【解决方案6】:

        我刚刚在免费命令行 bcc5.5.1 和 C++ Builder 6 bcc5.64 上对此进行了测试,它们都按预期工作——考虑到它们的年龄,这令人惊讶。然后我在 C++ Builder 2007, bcc5.93 中尝试了这个,并且那里存在错误。事实上,示例代码可以简化为原始类型,并且错误仍然存​​在:

        class D
        {
        public:
            D();
        
        private:
            std::auto_ptr<int>      a_;
            std::auto_ptr<short>    b_;
            std::auto_ptr<char>     c_;
            std::auto_ptr<bool>     d_;
        };
        

        这个极端的例子最终导致 C 类没有调用相应的析构函数!如果您有兴趣进一步诊断此错误,您可以执行的一个技巧是在 D::D() ctor 中插入程序集断点:

        // Note that D::D() ctor can't be inlined if it contains assembly
        // limitation of borland compilers unfortunately
        D::D()
        {
            __asm int 3;
            C c1(1);
            C c2(2);
            C c3(3);
        
            throw my_error();
        }
        

        然后您可以让它通过调试器运行。当执行到达指定的断点时,程序将停止并将控制权转移回调试器。然后,您可以单步执行程序集以查看问题所在。

        【讨论】:

          【解决方案7】:

          看起来像是异常处理堆栈展开代码中的一个错误。尝试在 D 的构造函数中使用它的实例创建一个简单的类 E,看看它是否被调用。

          【讨论】:

            猜你喜欢
            • 2018-10-11
            • 1970-01-01
            • 2017-05-19
            • 1970-01-01
            • 2019-07-22
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多