7

In the following backtrace from a core dump A2:~A2 is called twice:

#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 is derived from A1 and B2 is derived from B1. Only B2 has a default destructor, all base class destructors are virtual.

The code looks something like this:

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
//...
}

I cannot understand why A2::~A2 is called twice for the same object ( the this pointer in the backtrace has the same value for both 4 and 5 frames).

If I go to frame 4 and disassemble it prints a very different result from the frame 5 disassembeled code (about 90 lines of assembly code vs about 20 lines of assembly code).

George
  • 1,027
  • 3
  • 12
  • 20
  • 1
    Unrelated: `delete[]`ing a null pointer does nothing, so the if is pointless. – R. Martinho Fernandes Feb 05 '13 at 15:45
  • 1
    `A1 *myA1= new A2();` is local to the constructor, so you cannot access it inside `void E::Identify()` unless you pass it as a parameter(*which you don't*). Are you sure this is your original code? There is a disparity between the code you show and the Q you ask. – Alok Save Feb 05 '13 at 15:47
  • 4
    Did you add some code (like cout) to ~A2 and observed it being executed twice? Occuring twice in the stacktrace is rather meaningless, as the compiler often splits up dtors into multiple "thunks". You can see that by checking the addresses in the backtrace, or by using `nm` to look for ~A2 symbols. – PlasmaHH Feb 05 '13 at 15:49
  • Thank you Alok, I will update this. This is not the original code as it is an excerpt from a larger project that I do not own or wrote. I just wrote here the relevant parts. – George Feb 05 '13 at 15:50
  • Did you compile with optimizations? The address may be in a register that has been reused. – jxh Feb 05 '13 at 15:52
  • 1
    Have you followed the **[Rule of Three](http://stackoverflow.com/questions/4172722/what-is-the-rule-of-three)**? This distinctly smells of failure to adhering to it. – Alok Save Feb 05 '13 at 15:52
  • 1
    @PlasmaHH : that sounds like it should be an answer, rather than a comment. – Sander De Dycker Feb 05 '13 at 16:20
  • Thank you @PlasmaHH , I think you are right. My only hint is this backtrace. The addresses of the assembly instructions are continuous from frame 4 to frame 5. This should be the answer. – George Feb 05 '13 at 16:51

2 Answers2

7

I minimalized the example to

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

class A2 : public A1 {
};

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

with the assert to trigger a core dump.

Compiling with g++ 4.7.2, we get the double destructor backtrace in 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

while the backtrace of the same code compiled with g++ 4.3.2 looks similar but with only one frame for A2::~A2.

Both backtraces extracted with the same version of gdb (7.5.1).

So it is an artifact of the code generated by g++ 4.7, nothing to worry about for the behaviour of the compiled binary. It is not a real double call to the destructor.

il_guru
  • 8,383
  • 2
  • 42
  • 51
sgadrat
  • 86
  • 2
2

This could be your scenario (but you didn't show us this part of the code)...

If class E holds a private member pointer to A2, and it doesn't have a copy constructor or operator= ....

Then, there could be a situation where an Object of type E is copied to another object (variable) of type E with the default copy constructor or operator =.

That will cause shallow copying of members, which will cause both objects to now point to the same object A1.

When the object E's are destroyed, they both try to delete the same A2 object.

Yochai Timmer
  • 48,127
  • 24
  • 147
  • 185