为什么虚拟表中有两个虚拟析构函数,其中是非虚函数的地址(gcc4.6.3)

min*_*ain 24 c++ gcc

我实现了一个简单的测试来检查Derived类的内存等级,所以我发现Derive类的虚拟表中有两个虚拟析构函数地址.有人可以向我解释一下吗?

码:

#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;
}
Run Code Online (Sandbox Code Playgroud)

我发现了

f() and f2()
Run Code Online (Sandbox Code Playgroud)

结果如下:

~Derive
~Base2
~Base1
~Derive
~Base2
~Base1
Run Code Online (Sandbox Code Playgroud)

派生类的析构函数.为什么有两个?

我还有另一个问题:非虚拟成员函数的地址在哪里?我发现派生类的内存中不存在非虚函数地址.它在哪里?

Bor*_*der 26

非虚拟成员函数的地址,你说它,它不是虚拟的,这意味着它不需要在虚拟表中.为什么?那么它不依赖于对象的运行时类型,只有静态类型意味着编译器可以在编译时找出要调用的函数,以便解析调用,而不是在执行期间使用后期绑定.函数本身位于某处的代码部分,因此在编译时,函数地址直接插入调用站点.

好了,现在好玩的东西.我在视觉工作室观察列表中做了一些挖掘,这是我发现的:

|---------------------------|
|          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)        |
|---------------------------|
Run Code Online (Sandbox Code Playgroud)

所以是的,你有两次你的析构函数,基本上每个基础一次.如果我删除Derive的第二个基础(使它只从Base1继承),我们得到:

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

|---------------------------|
|       Base1 vtable        |
|---------------------------|
| Derive::destructor (+0)   |
| Derive::print (+4)        |
|---------------------------|
Run Code Online (Sandbox Code Playgroud)

以下是观察列表和本地窗口的屏幕截图.如果您查看监视列表中的值,您将看到对象Derive的开始与a的地址之间存在间隙,即第一个vtable适合的位置(Base1的位置).其次你会发现a和b之间的差距相同,那就是第二个vtable适合的位置(Base2的那个). 在此输入图像描述 编辑:EUREKA!

好吧,所以我在Windows上使用QtCreator在gcc中运行此代码-fdump-class-hierarchy,这给了我:

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
Run Code Online (Sandbox Code Playgroud)

所以我们可以清楚地看到确实有2个条目是Derive类的析构函数.这仍然没有回答为什么我们一直在寻找的东西.好吧,我在GCC的Itanium ABI中发现了这个

虚拟析构函数的条目实际上是条目对.第一个析构函数,称为完整对象析构函数,在不调用对象的delete()的情况下执行销毁.第二个析构函数,称为删除析构函数,在销毁对象后调用delete().两者都破坏任何虚拟基地; 一个单独的非虚函数,称为基础对象析构函数,执行对象的销毁但不执行其虚拟基础子对象,并且不调用delete().

因此,为什么有两个的理由似乎是这样的:说我有A,B.B继承自A.当我在B上调用delete时,删除虚拟析构函数是对B的调用,但是非删除虚拟析构函数将是要求A或否则会有双重删除.

我个人原本希望gcc只生成一个析构函数(一个非删除的析构函数),然后调用delete.这可能是VS所做的,这就是为什么我只在我的vtable中找到一个析构函数而不是两个.

好吧我现在可以去睡觉了:)希望这能满足你的好奇心!