我实现了一个简单的测试来检查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;
}
我找到了
f() and f2()
结果如下:
~Derive
~Base2
~Base1
~Derive
~Base2
~Base1
是派生类的析构函数。为什么有两个?
我还有另一个问题:非虚拟成员函数的地址在哪里?我发现派生类的内存中不存在非虚函数地址。它在哪里?
答案 0 :(得分:24)
非虚拟成员函数的地址,你说它,它不是虚拟的,这意味着它不需要在虚拟表中。为什么?那么它不依赖于对象的运行时类型,只有静态类型意味着编译器可以在编译时找出要调用的函数,以便解析调用,而不是在执行期间使用后期绑定。函数本身位于代码部分,因此在编译时,函数地址直接插入调用站点。
好了,现在开始玩这些有趣的东西了。我在视觉工作室观察列表中进行了一些挖掘,这是我发现的:
|---------------------------|
| 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的那个)。 编辑:EUREKA!
好吧,所以我使用-fdump-class-hierarchy
在Windows上使用QtCreator在gcc中运行此代码,这给了我:
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
所以我们可以清楚地看到确实有两个条目是Derive类的析构函数。这仍然没有回答为什么我们一直在寻找的东西。好吧,我在GCC's Itanium ABI
中找到了这个虚拟析构函数的条目实际上是条目对。第一个析构函数,称为完整对象析构函数,在不调用对象的delete()的情况下执行销毁。第二个析构函数,称为删除析构函数,在销毁对象后调用delete()。两者都破坏任何虚拟基地;一个单独的非虚函数,称为基础对象析构函数,执行对象的销毁但不执行其虚拟基础子对象,并且不调用delete()。
所以,为什么有两个似乎是这样的理由:说我有A,B。B继承自A.当我在B上调用delete时,删除虚拟析构函数是对B的调用,但是不删除一个人将被召唤为A,否则会有双重删除。
我个人原本希望gcc只生成一个析构函数(一个非删除的析构函数),然后调用delete。这可能是VS所做的,这就是为什么我只在我的vtable中找到一个析构函数而不是两个。
好吧我现在可以去睡觉了:)希望这能满足你的好奇心!
答案 1 :(得分:0)
这必须是针对您的编译器的特定情况,我在g ++中也是如此,并且仅导致一个析构函数调用。
~Derive
~Base2
~Base1
链接到结果: https://ideone.com/vzZggh
是的,对于非虚拟成员函数,您需要进入基类。
派生类的内存中不存在非虚拟函数地址。