探索C++对象模型(二)

2014-11-24 10:13:16 · 作者: · 浏览: 1
fun2:
#include

using namespace std;

class A
{
public:
void fun1(){ cout << "fun1"; }
virtual void fun2() { cout << "fun2"; }
virtual ~A() {}

char m_cA;
int m_nA;
static int s_nCount;
};

int A::s_nCount = 0;

class B: public A
{
public:
virtual void fun2() { cout << "fun2 in B"; }
virtual void fun3() { cout << "fun3 in B"; }

public:
int m_nB;
};

int main()
{
B* p = new B;
A* p1 = p;

p1->fun2();

system("pause");

return 0;
}
用原来的方法进行调试,查看B对象的内存布局
0:000> dt p
Local var @ 0x13ff74 Type B*
0x00034640
+0x000 __VFN_table : 0x004161d8
+0x004 m_cA : 120 'x'
+0x008 m_nA : 0n0
=0041c3e0 A::s_nCount : 0n0
+0x00c m_nB : 0n0
可以看到B对象的大小是原来A对象的大小加4(m_nB), 总共是16字节,查看B的虚表内容如下:
0:000> dps 0x004161d8
004161d8 00401080 ConsoleTest!B::fun2 [f:\test\consoletest\consoletest\consoletest.cpp @ 26]
004161dc 004010c0 ConsoleTest!B::`scalar deleting destructor'
004161e0 004010a0 ConsoleTest!B::fun3 [f:\test\consoletest\consoletest\consoletest.cpp @ 27]
004161e4 326e7566
可以看到虚表中保存的都是B的虚函数地址: fun2(), ~B(), fun3()

结论: 单继承时父类和子类共用同一虚表指针,而子类的数据被添加在父类数据之后,父类和子类的对象指针在相互转化时值不变。


多继承对象内存模型

我们把上面的代码改成多继承的方式, class A, class B, 然后C继承A和B:
#include
using namespace std;
class A
{
public:
virtual void fun() {cout << "fun in A";}
virtual void funA() {cout << "funA";}
virtual ~A() {}
char m_cA;
int m_nA;
static int s_nCount;
};
int A::s_nCount = 0;
class B
{
public:
virtual void fun() {cout << "fun in B";}
virtual void funB() {cout << "funB";}
int m_nB;
};
class C: public A, public B
{
public:
virtual void fun() {cout << "fun in C";};
virtual void funC(){cout << "funC";}
int m_nC;
};
int main()
{
C* p = new C;
B* p1 = p;
p->fun();
system("pause");
return 0;
}
依旧用原来的方式调试,查看C的内存布局
0:000> dt p
Local var @ 0x13ff74 Type C*
0x00034600
+0x000 __VFN_table : 0x004161e4
+0x004 m_cA : 120 'x'
+0x008 m_nA : 0n0
=0041c3e0 A::s_nCount : 0n0
+0x00c __VFN_table : 0x004161d8
+0x010 m_nB : 0n0
+0x014 m_nC : 0n0
可以看到C对象由0x18 = 24字节组成,可以看到数据依次是虚表指针,A的数据,虚表指针, B的数据, C的数据。

查看第一个虚表内容:
0:000> dps 0x004161e4
004161e4 004010f0 ConsoleTest!C::fun [f:\test\consoletest\consoletest\consoletest.cpp @ 36]
004161e8 004010b0 ConsoleTest!A::funA [f:\test\consoletest\consoletest\consoletest.cpp @ 13]
004161ec 00401130 ConsoleTest!C::`scalar deleting destructor'
004161f0 00401110 ConsoleTest!C::funC [f:\test\consoletest\consoletest\consoletest.cpp @ 37]
004161f4 416e7566
可以看到前面虚表的前面3个虚函数符合A的虚表要求,最后加上了C的新增虚函数funC, 所以该虚表同时符合A和C的要求,也就是说A和C共用同一个虚表指针。

再看第二个虚表内容:
0:000> dps 0x004161d8
004161d8 00401560 ConsoleTest![thunk]:C::fun`adjustor{12}'
004161dc 004010d0 ConsoleTest!B::funB [f:\test\consoletest\consoletest\consoletest.cpp @ 28]
可以看到第二个虚表符合B的虚表要求,并且把B的虚函数fun用C的改写了,所以它是给B用的。

我们再看基类对象B的布局情况:
0:000> dt p1
Local var @ 0x13ff70 Type B*
0x003e460c
+0x000 __VFN_table : 0x004161d8
+0x004 m_nB : 0n0
我们可以看到p1指针本身在堆栈上的地址是0x13ff70,而p1所指向对象的地址是0x003e460c ,所以将C指针转成B指针后,B的地址和C的地址之差是0x003e460c- 0x00034600 = 0xc = 12字节, 也就是说B的指针p1指向我们上面的第二个虚表指针。

结论: 多重继承时派生类和第一个基类公用一个虚表指针,他们的对象指针相互转化时值不变;而其他基类(非第一个)和派生类的对象指针在相互转化时有一定的偏移。