Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

why there are two virtual destructor in the virtual table and where is address of the non-virtual function (gcc4.6.3)

Tags:

c++

gcc

I implemented a simple test to check the memory rank of Derived class, so I find there are two virtual destructor address in the virtual table of Derive class. Can someone explain it to me?

Code:

#include<iostream> #include<ostream> #include<cstdio> using namespace std;  class Base1 {     public:         Base1():a(1){}         virtual ~Base1()         {             cout << "~Base1"  << endl;         }         int a;         virtual void print()         {             cout << "I am base 1!" << endl;         } };  class Base2 {     public:         Base2():b(2){}         virtual ~Base2(){             cout << "~Base2" << endl;         }         int b;         virtual void print()         {             cout << "I am base 2!" << endl;         } };  class Derive : public Base1, public Base2 {     public:         Derive():c(3){}         virtual ~Derive(){             cout << "~Derive" << endl;         }         int c;         virtual void print()         {             cout << "I am Derive!!" << endl;         }         void prints()         {             cout << "I am not virtual!!" << endl;         } };  int main() {     typedef void (*Func) (void);     Derive *d = new Derive();     int **p = (int **)(d);     Func f = (Func)(p[0][0]);     //int s = (int)(*(p + 3));     Func f2 = (Func)(p[0][1]);     //cout << p << endl;     //cout << s << endl;     f();     //cout.flush();     f2();     //f();     return 0; } 

I find the

f() and f2() 

The result is as follow:

~Derive ~Base2 ~Base1 ~Derive ~Base2 ~Base1 

are destructors of derived class. Why are there two?

And I have another question: Where is the address of the non-virtual member function? I find the non-virtual function address does not exist in the memory of the derived class. Where is it?

like image 810
minicaptain Avatar asked Jul 31 '13 03:07

minicaptain


People also ask

Why there are two virtual destructor in the virtual table?

EDIT: EUREKA! The entries for virtual destructors are actually pairs of entries. The first destructor, called the complete object destructor, performs the destruction without calling delete() on the object. The second destructor, called the deleting destructor, calls delete() after destroying the object.

What is VPTR and Vtable where it stored?

Vtable and Vptr is creating at compile time which will get memory in run time and vtable entries are virtual function addresses . Every object of a class containing a virtual function will have an extra pointer which is pointing to Virtual Table is known as virtual pointer.

What is the basic purpose of virtual destructor why and when we have to implement it write briefly?

A virtual destructor is used to free up the memory space allocated by the derived class object or instance while deleting instances of the derived class using a base class pointer object.

Why does a destructor in base class need to be declared virtual?

A virtual destructor is needed when you delete an object whose dynamic type is DerivedClass by a pointer that has type BaseClass* . The virtual makes the compiler associate information in the object making it able to execute the derived class destructor. Missing the virtual in such case causes undefined behavior.


1 Answers

The address of the non-virtual member function, well you said it, it's not virtual which means it doesn't need to be in the virtual table. Why? Well it doesn't depend on the runtime type of the object, only the static type meaning the compiler can figure out at compile time which function to call so the call is resolved then instead of using late binding during execution. The function itself is in the code section somewhere and so at compile time the functions address is inserted at the call site directly.

Ok now onto the fun stuff. I did some digging around in the visual studio watch list and here is what I found:

|---------------------------| |          Derive           | |---------------------------| | vtable ptr for Base1 (+0) | | Base1::a (+4)             | |---------------------------| | vtable ptr for Base2 (+8) | | Base2::b (+12)            | |---------------------------| | Derive::c (+16)           | |---------------------------|  |---------------------------| |       Base1 vtable        | |---------------------------| | Derive::destructor (+0)   | | Derive::print (+4)        | |---------------------------|  |---------------------------| |       Base2 vtable        | |---------------------------| | Derive::destructor (+0)   | | Derive::print (+4)        | |---------------------------| 

So yeah you have your destructor twice, once per base essentially. If I remove the second base of Derive (making it only inherit from Base1) we get:

|---------------------------| |          Derive           | |---------------------------| | vtable ptr for Base1 (+0) | | Base1::a (+4)             | |---------------------------| | Derive::c (+8)            | |---------------------------|  |---------------------------| |       Base1 vtable        | |---------------------------| | Derive::destructor (+0)   | | Derive::print (+4)        | |---------------------------| 

Here is a screenshot of the watch list and locals window. If you take a look at the values in the watch list you'll see theres a gap between the start of the object Derive and the address of a, that is where the first vtable fits (the one for Base1). And secondly you'll find the same gap between a and b, that's where the second vtable fits (the one for Base2). enter image description here EDIT: EUREKA!

Alright, so I ran this code in gcc using QtCreator on Windows with -fdump-class-hierarchy and this gave me:

Vtable for Derive Derive::_ZTV6Derive: 10u entries 0     (int (*)(...))0 4     (int (*)(...))(& _ZTI6Derive) 8     (int (*)(...))Derive::~Derive 12    (int (*)(...))Derive::~Derive 16    (int (*)(...))Derive::print 20    (int (*)(...))-8 24    (int (*)(...))(& _ZTI6Derive) 28    (int (*)(...))Derive::_ZThn8_N6DeriveD1Ev 32    (int (*)(...))Derive::_ZThn8_N6DeriveD0Ev 36    (int (*)(...))Derive::_ZThn8_N6Derive5printEv 

So we can clearly see that there are indeed 2 entries which are destructors of class Derive. This still doesn't answer why yet which is what we've been searching for all along. Well, I found this in GCC's Itanium ABI

The entries for virtual destructors are actually pairs of entries. The first destructor, called the complete object destructor, performs the destruction without calling delete() on the object. The second destructor, called the deleting destructor, calls delete() after destroying the object. Both destroy any virtual bases; a separate, non-virtual function, called the base object destructor, performs destruction of the object but not its virtual base subobjects, and does not call delete().

So, the rationale for why there are two seems to be this: Say I have A, B. B inherits from A. When I call delete on B, the deleting virtual destructor is call for B, but the non-deleting one will be called for A or else there would be a double delete.

I personally would have expected gcc to generate only one destructor (a non-deleting one) and call delete after instead. This is probably what VS does, which is why I was only finding one destructor in my vtable and not two.

Alright I can go to bed now :) Hope this satisfies your curiosity!

like image 192
Borgleader Avatar answered Sep 19 '22 03:09

Borgleader