浅谈VC2003对C++虚继承/共享继承实现


在虚继承/共享继承中,类对象,会额外增加一个表头指针——虚基类表。

这里设定的虚继承模型为:



class A

#if !defined(_A_H)
#define _A_H

/**
  * \brief    A类。
  */
class A
{
public:
	
	virtual void TestA()
	{
		printf( "TestA\r\n" );
	}

	char cA;
};

#endif  // _A_H

class B

#if !defined(_B_H)
#define _B_H

/**
  * \brief    B类。
  */
class B
{
public:

	virtual void TestB()
	{
		printf( "TestB\r\n" );
	}

	char cB;	
};

#endif  // _B_H

class C : public virtual A, public virtual B

#if !defined(_C_H)
#define _C_H

#include "A.h"
#include "B.h"

/**
  * \brief    C类。
  */
class C : public virtual A, public virtual B
{
public:

	virtual void TestC()
	{
		printf( "TestC\r\n" );
	}

	char cC;
};

#endif  // _C_H

class D : public virtual A, public virtual B

#if !defined(_D_H)
#define _D_H

#include "A.h"
#include "B.h"

/**
  * \brief    D类。
  */
class D : public virtual A, public virtual B
{
public:

	virtual void TestD()
	{
		printf( "TestD\r\n" );
	}


	char cD;
};

#endif  // _D_H

class E : public C, public D
#if !defined(_E_H)
#define _E_H

#include "C.h"
#include "D.h"

/**
  * \brief    E类。
  */
class E : public C, public D
{
public:

	virtual void TestE()
	{
		printf( "TestE\r\n" );
	}

	char cE;
};

#endif  // _E_H

目的是想通过反汇编看一下,VC2003把虚继承如何实现,而在这期间,虚基类表起到什么作用。

根据以上模型,
首先,看一下多继承中,对象的内存布局。址址由低到高。

C的虚函数表
C的虚基类表
C的成员
D的虚函数表
D的虚基类表
D的成员
E的成员
A的虚函数表
A的成员
B的虚函数表
B的成员


f8 50 42 00                   // + __vfptr0x004250f8 const E::`vftable'{for `C'}*
14 51 42 00                   // 0x00425114  C's vbptr
03 cc cc cc                   // cC
ec 50 42 00                   // + __vfptr0x004250ec const E::`vftable'{for `D'}*
04 51 42 00                   // 0x00425104  D's vbptr
04 cc cc cc                   // cD
05 cc cc cc                   // cE
e0 50 42 00                   // + __vfptr0x004250e0 const E::`vftable'{for `A'}*
01 cc cc cc                   // cA
d4 50 42 00                   // + __vfptr0x004250d4 const E::`vftable'{for `B'}*
02 cc cc cc                   // cB


00411CF5  call        A::TestA (4110B9h)
00411D04  call        B::TestB (4113ACh) 
00411D0C  call        C::TestC (41150Fh) 
00411D14  call        D::TestD (411069h) 
00411D1C  call        E::TestE (411262h) 


再看看虚函数表&虚基类表的内存情况。
+ __vfptr 0x004250d4 const E::`vftable'{for `B'} *
ac 13 41 00                   // B::TestB (4113ACh)
00 00 00 00
cc 7a 42 00                   // 
+ __vfptr 0x004250e0 const E::`vftable'{for `A'} *
b9 10 41 00                   // A::TestA (4110B9h)
00 00 00 00
e4 7a 42 00                   // 
+ __vfptr 0x004250ec const E::`vftable'{for `D'} *
69 10 41 00                   // D::TestD (411069h) 
00 00 00 00 
c0 7b 42 00                   // 
+ __vfptr 0x004250f8 const E::`vftable'{for `C'} *
0f 15 41 00                   // C::TestC (41150Fh) 
62 12 41 00                   // E::TestE (411262h) 
00 00 00 00
0x00425104  D's vbptr
fc ff ff ff                   // 
0c 00 00 00                   // D对象转换为A对象时使用的内存偏移值(编译期间确定)
14 00 00 00                   // D对象转换为B对象时使用的内存偏移值(编译期间确定)

00 00 00 00 
0x00425114  C's vbptr
fc ff ff ff 
18 00 00 00                   // C对象转换为A对象时使用的内存偏移值(编译期间确定)
20 00 00 00                   // D对象转换为B对象时使用的内存偏移值(编译期间确定)

00 00 00 00 
10 7c 42 00                   // 
b9 10 41 00                   // A::TestA (4110B9h)
00 00 00 00
60 7c 42 00                   // 
ac 13 41 00                   // B::TestB (4113ACh)
00 00 00 00 
78 7c 42 00                   // 
ac 13 41 00                   // B::TestB (4113ACh)
00 00 00 00 
90 7c 42 00                   // 
b9 10 41 00                   // A::TestA (4110B9h)
00 00 00 00 
cc 7c 42 00                   // 
0f 15 41 00                   // C::TestC (41150Fh) 
00 00 00 00 
fc ff ff ff
08 00 00 00 
10 00 00 00
00 00 00 00


好了,有了内存结构,以下来看一下,以各对象转换的汇编代码。
我也好久没有看汇编了,只记得部份。只在关键部份把注释加上,帮助阅读。


A* pA = &e;
00411B95  lea         eax,[e] 
00411B98  test        eax,eax 
00411B9A  jne         main+0A8h (411BA8h) 
00411B9C  mov         dword ptr [ebp-164h],0 
00411BA6  jmp         main+0B8h (411BB8h) 
00411BA8  mov         ecx,dword ptr [ebp-2Ch] 
00411BAB  mov         edx,dword ptr [ecx+4]                           // 此处,edx保存了18 00 00 00// C对象转换为A对象时使用的内存偏移值
00411BAE  lea         eax,[ebp+edx-2Ch] 
00411BB2  mov         dword ptr [ebp-164h],eax 
00411BB8  mov         ecx,dword ptr [ebp-164h] 
00411BBE  mov         dword ptr [pA],ecx 
B* pB = &e;
00411BC1  lea         eax,[e] 
00411BC4  test        eax,eax 
00411BC6  jne         main+0D4h (411BD4h) 
00411BC8  mov         dword ptr [ebp-164h],0 
00411BD2  jmp         main+0E4h (411BE4h) 
00411BD4  mov         ecx,dword ptr [ebp-2Ch] 
00411BD7  mov         edx,dword ptr [ecx+8]                          // 此处,edx保存了20 00 00 00// C对象转换为B对象时使用的内存偏移值 
00411BDA  lea         eax,[ebp+edx-2Ch] 
00411BDE  mov         dword ptr [ebp-164h],eax 
00411BE4  mov         ecx,dword ptr [ebp-164h] 
00411BEA  mov         dword ptr [pB],ecx 
C* pC = &e;
00411BED  lea         eax,[e] 
00411BF0  mov         dword ptr [pC],eax                              // 这里不需要转换
D* pD = &e;
00411BF3  lea         eax,[e] 
00411BF6  test        eax,eax 
00411BF8  je          main+105h (411C05h) 
00411BFA  lea         ecx,[ebp-24h]                                   // 这里编译时就已经知道D对象和E对象具体的偏移值,直接栈中照减
00411BFD  mov         dword ptr [ebp-164h],ecx 
00411C03  jmp         main+10Fh (411C0Fh) 
00411C05  mov         dword ptr [ebp-164h],0 
00411C0F  mov         edx,dword ptr [ebp-164h] 
00411C15  mov         dword ptr [pD],edx 
E* pE = &e;
00411C18  lea         eax,[e] 
00411C1B  mov         dword ptr [pE],eax 


A* pCA = pC;
00411C1E  cmp         dword ptr [pC],0 
00411C22  jne         main+130h (411C30h) 
00411C24  mov         dword ptr [ebp-164h],0 
00411C2E  jmp         main+146h (411C46h) 
00411C30  mov         eax,dword ptr [pC] 
00411C33  mov         ecx,dword ptr [eax+4] 
00411C36  mov         edx,dword ptr [ecx+4] 
00411C39  mov         eax,dword ptr [pC] 
00411C3C  lea         ecx,[eax+edx+4] 
00411C40  mov         dword ptr [ebp-164h],ecx 
00411C46  mov         edx,dword ptr [ebp-164h] 
00411C4C  mov         dword ptr [pCA],edx 
A* pDA = pD;
00411C4F  cmp         dword ptr [pD],0 
00411C53  jne         main+161h (411C61h) 
00411C55  mov         dword ptr [ebp-164h],0 
00411C5F  jmp         main+177h (411C77h) 
00411C61  mov         eax,dword ptr [pD] 
00411C64  mov         ecx,dword ptr [eax+4] 
00411C67  mov         edx,dword ptr [ecx+4] 
00411C6A  mov         eax,dword ptr [pD] 
00411C6D  lea         ecx,[eax+edx+4] 
00411C71  mov         dword ptr [ebp-164h],ecx 
00411C77  mov         edx,dword ptr [ebp-164h] 
00411C7D  mov         dword ptr [pDA],edx 
B* pDB = pD;
00411C83  cmp         dword ptr [pD],0 
00411C87  jne         main+195h (411C95h) 
00411C89  mov         dword ptr [ebp-164h],0 
00411C93  jmp         main+1ABh (411CABh) 
00411C95  mov         eax,dword ptr [pD] 
00411C98  mov         ecx,dword ptr [eax+4] 
00411C9B  mov         edx,dword ptr [ecx+8] 
00411C9E  mov         eax,dword ptr [pD] 
00411CA1  lea         ecx,[eax+edx+4] 
00411CA5  mov         dword ptr [ebp-164h],ecx 
00411CAB  mov         edx,dword ptr [ebp-164h] 
00411CB1  mov         dword ptr [pDB],edx 
B* pCB = pC;
00411CB7  cmp         dword ptr [pC],0 
00411CBB  jne         main+1C9h (411CC9h) 
00411CBD  mov         dword ptr [ebp-164h],0 
00411CC7  jmp         main+1DFh (411CDFh) 
00411CC9  mov         eax,dword ptr [pC] 
00411CCC  mov         ecx,dword ptr [eax+4] 
00411CCF  mov         edx,dword ptr [ecx+8] 
00411CD2  mov         eax,dword ptr [pC] 
00411CD5  lea         ecx,[eax+edx+4] 
00411CD9  mov         dword ptr [ebp-164h],ecx 
00411CDF  mov         edx,dword ptr [ebp-164h] 
00411CE5  mov         dword ptr [pCB],edx 


以下是转换后的地各地址。
大家可能会奇怪,为什么pE和pC的地址是一样的?这个是因为C是E的第一个基类。这个可以通过各种C++丛书找到答案。
而其他类对象的地址则是通过虚基类表转换而来,并非直接得出,所以会影响效率。
+ pC 0x0012feac {cE=0x05 '?' }C *
+ pD 0x0012feb8 {cE=0x05 '?' }D *
+ pE 0x0012feac {cE=0x05 '?' }E *
+ pA 0x0012fec8 {cE=??? }A * 
                                            // 0x0012feac +  00 00 00 18
+ pB 0x0012fed0 {cE=??? }B *                                           //0x0012feac + 00000020



+ pCB 0x0012fed0 {cE=??? }B *
+ pDB 0x0012fed0 {cE=??? }B *
+ pCA 0x0012fec8 {cE=??? }A *
+ pDA 0x0012fec8 {cE=??? }A *


总结一下,虚继承实际上就是通过对虚派生对象增加一个虚父类表指针,然后在向上转型时,通过此虚父类表找到向上转型时具体父类的偏移值,做:
父类对象 = ( this + 虚父类表偏移值
[ + sizeof(int)]  );         // 当包括虚函数时。
运算得出具体向上转型父类对象。


本人理解不足的地方,希望大家指正,谢谢!
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值