【问题标题】:Why is the destructor called twice for the same object?为什么同一个对象要调用两次析构函数?
【发布时间】:2013-02-05 15:43:50
【问题描述】:

在以下来自核心转储 A2 的回溯中:~A2 被调用了两次:

#0  0x086f5371 in B1::~B1 (this=0xe6d3a030, 
    __in_chrg=<value optimized out>)
    at /fullpath/b1.cpp:400
#1  0x086ffd43 in ~B2 (this=0xe6d3a030, 
    __in_chrg=<value optimized out>)
    at /fullpath/b2.h:21
#2  B2::~B2 (this=0xe6d3a030, 
    __in_chrg=<value optimized out>)
    at /fullpath/b2.h:21
#3  0x086ea516 in A1::~A1 (this=0xe3e93958, 
    __in_chrg=<value optimized out>)
    at /fullpath/a1.cpp:716
#4  0x0889b85d in A2::~A2 (this=0xe3e93958, 
    __in_chrg=<value optimized out>)
    at /fullpath/a2.cpp:216
#5  0x0889b893 in A2::~A2 (this=0xe3e93958, 
    __in_chrg=<value optimized out>)
    at /fullpath/a2.cpp:216
#6  0x0862c0f1 in E::Identify (this=0xe8083e20, t=PT_UNKNOWN)
    at /fullpath/e.cpp:713

A2 是从 A1 派生的,B2 是从 B1 派生的。只有 B2 有默认析构函数,所有基类析构函数都是虚的。

代码如下所示:

e.cpp:

E::E(){
    //... some code ...
    myA1= new A2();
}

void E::Identify(){
    //...
    if(myA1){
        delete myA1; //line 713 of e.cpp
        myA1 = NULL;
    }

}

a2.cpp:

A2::~A2(){
    //...
    if (sd) //sd is not null here and also not made null after deletion
    {
        delete [] sd; //when called the second time shouldn't it crash here?
    }
    //...
} // line 216 of a2.cpp

a1.cpp

A1::A1(){
//...
   myB1 = new B2();
//...
}

A1::~A1(){
//...
    delete myB1; //line 716 of a1.cpp
//...
}

我不明白为什么 A2::~A2 会为同一个对象调用两次(回溯中的 this 指针对于 4 帧和 5 帧具有相同的值)。

如果我转到第 4 帧并反汇编,它会打印出与第 5 帧反汇编代码截然不同的结果(大约 90 行汇编代码与大约 20 行汇编代码)。

【问题讨论】:

  • 不相关:delete[]空指针什么都不做,所以 if 没有意义。
  • A1 *myA1= new A2(); 对于构造函数来说是本地的,所以你不能在 void E::Identify() 中访问它,除非你把它作为参数传递(你没有)。你确定这是你的原始代码吗?您显示的代码与您询问的 Q 之间存在差异。
  • 您是否在 ~A2 中添加了一些代码(如 cout)并观察到它被执行了两次?在堆栈跟踪中出现两次相当没有意义,因为编译器经常将 dtor 拆分为多个“thunk”。您可以通过检查回溯中的地址或使用 nm 查找 ~A2 符号来查看。
  • 你关注Rule of Three了吗?这明显是未能遵守它的味道。
  • @PlasmaHH :听起来应该是答案,而不是评论。

标签: c++ gdb


【解决方案1】:

我将示例最小化为

#include <cassert>
class A1 {
public:
    virtual ~A1() {
        assert(false);
    }
};

class A2 : public A1 {
};

int main() {
    A1* a = new A2;
    delete a;
    return 0;
}

使用断言触发核心转储。

用g++ 4.7.2编译,得到gdb中的双重析构函数回溯

#0  0x00007f16060e92c5 in raise () from /usr/lib/libc.so.6
#1  0x00007f16060ea748 in abort () from /usr/lib/libc.so.6
#2  0x00007f16060e2312 in __assert_fail_base () from /usr/lib/libc.so.6
#3  0x00007f16060e23c2 in __assert_fail () from /usr/lib/libc.so.6
#4  0x00000000004007c8 in A1::~A1 (this=0xf60010, __in_chrg=<optimized out>) at double.cpp:6
#5  0x000000000040084d in A2::~A2 (this=0xf60010, __in_chrg=<optimized out>) at double.cpp:10
#6  0x0000000000400880 in A2::~A2 (this=0xf60010, __in_chrg=<optimized out>) at double.cpp:10
#7  0x000000000040078c in main () at double.cpp:15

虽然使用 g++ 4.3.2 编译的相同代码的回溯看起来相似,但 A2::~A2 只有一帧。

使用相同版本的 gdb (7.5.1) 提取两个回溯。

所以它是 g++ 4.7 生成的代码的工件,无需担心编译后的二进制文件的行为。这不是对析构函数的真正双重调用。

【讨论】:

    【解决方案2】:

    这可能是您的场景(但您没有向我们展示这部分代码)...

    如果类 E 拥有指向 A2 的私有成员指针,并且它没有复制构造函数或 operator= ....

    然后,可能会出现这样一种情况,即使用默认的复制构造函数或运算符 = 将类型 E 的对象复制到类型 E 的另一个对象(变量)。

    这将导致成员的浅拷贝,这将导致两个对象现在都指向相同的对象 A1。

    当对象 E 被销毁时,它们都试图删除 同一个 A2 对象。

    【讨论】:

    • 对析构函数的调用不会在同一个堆栈跟踪中结束。
    猜你喜欢
    • 2015-10-09
    • 2016-11-23
    • 2021-11-06
    • 2021-09-03
    • 2013-12-11
    • 2011-02-07
    • 2016-09-06
    相关资源
    最近更新 更多