Thursday, April 14, 2022

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

Issue

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?


Solution

Non-virtual member function

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.

Virtual destructors in Visual Studio

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). vptr in visual studio

Virtual destructors in GCC

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!



Answered By - Borgleader
Answer Checked By - Mildred Charles (WPSolving Admin)