现在的位置: 首页 > 综合 > 正文

c++ 虚函数表

2013年08月28日 ⁄ 综合 ⁄ 共 2386字 ⁄ 字号 评论关闭

先看以下单继承类层次:

class A1

{

public:

      
A1(){}

      
virtual fun(){}

      
virtual funA1(){}

      
long m_data1;

};

 

class A2:public A1

{

public:

      
A2(){}

      
virtual fun(){}

      
virtual funA2(){}

      
long m_data2;

};

 

class A3:public A2

{

public:

      
A3(){}

      
virtual fun(){}

      
virtual funA3(){}

      
long m_data3;

};

初始化以下变量

A1 a1;

A2 a2;

A3 a3;

此时
a1,a2,a3
在内存中的结构将如下图所示

a1:

vfptr (
虚函数表指针,指向虚函数表
vftable1)

4
字节

m_data1

4
字节

 

a2:

vfptr (
虚函数表指针,指向虚函数表
vftable2)

4
字节

m_data1

4
字节

m_data2

4
字节

 

a3:

vfptr (
虚函数表指针,指向虚函数表
vftable3)

4
字节

m_data1

4
字节

m_data2

4
字节

m_data3

4
字节

 

虚函数表结构

vftable1:

函数
A1:fun()
地址

4
字节

函数
A1:funA1()
地址

4
字节

vftable2:

函数
A2:fun()
地址

4
字节

函数
A1:funA1()
地址

4
字节

函数
A2:funA2()
地址

4
字节

vftable3:

函数
A3:fun()
地址

4
字节

函数
A1:funA1()
地址

4
字节

函数
A2:funA2()
地址

4
字节

函数
A3:funA3()
地址

4
字节

 

每个
C++
类(基类或者
派生类
)在内存中对应着一个虚函数表(
vftable
),无论有没有对该类初始化变量,这个虚函数表都始终存在。

从上图可以看出,一个类无论有多少个基类,其相应实例的虚函数表指针始终只有一个,并且严格指向这个类(而不是基类)的虚函数表。

每个派生类的
虚函数表
继承了它所有基类的
虚函数表
,如果基类
虚函数表
中包含某一项,则其派生类的
虚函数表
中也将包含同样的一项,但是两项的值可能不同。如果派生类重载(override)了该项对应的虚函数,则派生类

虚函数表
的该项指向重载后的虚函数,没有重载的话,则沿用基类
虚函数表
的值。

 

再看以下代码

A3 a3;

A1 *pA1 = &a3;

A2 *pA2 = &a3;

A3 *pA3 = &a3;

不用说,你肯定知道
pA1,pA2,pA3
值一定相等。

 

接着我们看看以下多重继承类层次
:

class B1

{

public:

      
B1(){}

      
virtual fun(){}

      
virtual funB1(){}

      
long m_data1;

};

 

class B2

{

public:

      
B2(){}

      
virtual fun(){}

      
virtual funB2(){}

      
long m_data2;

};

 

class B3

{

public:

      
B3(){}

      
virtual fun(){}

      
virtual funB3(){}

      
long m_data3;

};

 

class C : public B1 , public B2, public B3

{

public:

      
C(){}

      
virtual fun(){}

      
virtual funC(){}

      
long m_dataC;

};

 

初始化变量:

      
C c;

此时变量
c
在内存中的结构将如下图所示:

vfptr (
虚函数表指针,指向虚函数表
vftable_C1)

4
字节

m_data1

4
字节

vfptr (
虚函数表指针,指向虚函数表
vftable_C2)

4
字节

m_data2

4
字节

vfptr (
虚函数表指针,指向虚函数表
vftable_C3)

4
字节

m_data3

4
字节

m_dataC

4
字节

 

虚函数表内存结构
::

vftableC1

函数
C:fun()
地址

4
字节

一份代码经过偏移修正的新的
B1:funB1()
函数地址

4
字节

一份代码经过偏移修正的新的
B2:funB2()
函数地址

4
字节

一份代码经过偏移修正的新的
B3:funB3()
函数地址

4
字节

函数
C:funC()
地址

4
字节

vftableC2

一份代码经过偏移修正的新的
C:fun()
函数地址

4
字节

函数
B2:funB2()
地址

4
字节

vftableC3

一个代码经过偏移修正的新的
C:fun()
函数地址

4
字节

函数
B3:funB3()
地址

4
字节

 

   上面的偏移修正指的是先进入一小段代码,
修改This指针(ECX寄存器),

然后再跳转到原始的调用过程

 

接着看以下代码

C c;

B1* pB1 = &c;

B2* pB2 = &c;

B3* pB3 = &c;

C*  
pC  
= &c;

 

假设变量
C
的开始地址为
address

这时指针值依次为
pB1=address

pB2=address+4

pB3=address+8

pC=address

参照上面变量
C
的内存结构,你可能已经猜到为什么需要多个虚函数表了

想想当使用这些地址不相同的指针调用一个成员函数时会发生什么,例如
pC->funB2(),

假如
funB2()
需要访问对象内部数据
m_data2
,这时按变量相对偏移计算地址时将出现错误。

 

关于
__declspec(novtable)

      

从上面例子你可以看到,如果一个基类只是作为抽象类(一般含纯虚函数),由于抽象类不能实例化对象,因此它的虚函数表将永远用不上,为节约内存空间,可以采用
__declspec(novtable)
指示编译器不为该抽象类生成虚函数表。

 

尾记:

大多数人可能没有必要钻这些细节,在大部分情况下,理解这篇文章对你可能有

些好处(比如ATL,那可是多重继承的聚集地),但可能于你的工作无多大帮助,
 
除非你正在打算写编译器或者设计
c++
之类。

抱歉!评论已关闭.