【问题标题】:why there are two virtual destructor in the virtual table and where is address of the non-virtual function (gcc4.6.3)为什么虚表中有两个虚析构函数,非虚函数的地址在哪里(gcc4.6.3)
【发布时间】:2013-07-31 03:08:34
【问题描述】:

我实现了一个简单的测试来检查派生类的内存等级,所以我发现派生类的虚拟表中有两个虚拟析构函数地址。谁能给我解释一下?

代码:

#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;
}

我找到了

f() and f2()

结果如下:

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

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

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

【问题讨论】:

    标签: c++ gcc


    【解决方案1】:

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

    好的,现在开始有趣的事情。我在视觉工作室观察列表中进行了一些挖掘,这是我发现的:

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

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

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

    这是监视列表和本地人窗口的屏幕截图。如果您查看监视列表中的值,您会发现 Derive 对象的开头和 a 的地址之间存在间隙,这是第一个 vtable 适合的位置(Base1 的那个)。其次,您会发现 a 和 b 之间的差距相同,这就是第二个 vtable 适合的地方(Base2 的那个)。 编辑:尤里卡!

    好的,所以我在 gcc 中使用 QtCreator 在 Windows 上使用-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
    

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

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

    所以,为什么有两个的理由似乎是这样的:假设我有 A,B。B 继承自 A。当我在 B 上调用 delete 时,删除虚拟析构函数是调用 B,但非删除将为 A 调用一个,否则会出现双重删除。

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

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

    【讨论】:

    • 真的吗?我检查了如果我有一个基类,我还将在虚拟表中获得两个派生类的析构函数!我知道虚表中不存在非虚拟成员函数,但我想知道它在哪里?非常感谢!
    • @minicaptain 我更新了我的答案。希望它能把事情弄清楚。
    • @Borgleader 其实,我知道派生类的内存等级,如果你看base1和base2的虚拟表,你会发现在Base1和Base2虚拟表中都有两个析构函数地址, 在 gcc 中!我想知道为什么?
    • @JimHurley 我真的很想接受它但我认为Borleader 没有回答我的第一个问题!如果你阅读Base1 和Base2 的虚拟表,它们都有两个派生类的析构函数,他没有给我理由!
    • 如果B继承A,那么B的析构函数将调用基础对象析构函数,它根本不在vtables中。两个vtable dtor条目的区别是因为删除多态对象需要根据派生最多的类型选择operator delete
    【解决方案2】:
    1. 为什么有两个?

    这必须是你的编译器的特定情况,我在 g++ 中做了同样的事情,结果只有一个析构函数调用。

    ~Derive
    ~Base2
    ~Base1
    

    结果链接: https://ideone.com/vzZggh

    1. 非虚成员函数的地址在哪里?

    是的,对于非虚拟成员函数,您需要转到基类。

    派生类的内存中不存在非虚函数地址。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2021-06-01
      • 2011-02-04
      • 2011-08-15
      • 2015-11-12
      • 2011-09-04
      • 2012-04-13
      • 2014-06-02
      • 2020-03-06
      相关资源
      最近更新 更多