Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why is the destructor called twice for the same object?

Tags:

c++

gdb

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).

like image 530
George Avatar asked Feb 05 '13 15:02

George


People also ask

Can destructor be called multiple times?

According to the linked question, this is because the destructor is called multiple times for the pointer member, a consequence of the copy constructor being called multiple times when copying, causing an attempted deallocation of memory already deallocated.

How many times destructor is called?

The destructor is being called three times, for a , lol and b . In your case, a and b are instantiated using the default constructor .

Why the name of destructor is same as class name?

Like a constructor, Destructor is also a member function of a class that has the same name as the class name preceded by a tilde(~) operator. It helps to deallocate the memory of an object. It is called while the object of the class is freed or deleted.

What is destructor why and how it is called?

Destructors are usually used to deallocate memory and do other cleanup for a class object and its class members when the object is destroyed. A destructor is called for a class object when that object passes out of scope or is explicitly deleted.


2 Answers

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.

like image 168
sgadrat Avatar answered Sep 22 '22 08:09

sgadrat


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.

like image 28
Yochai Timmer Avatar answered Sep 24 '22 08:09

Yochai Timmer