虚函数表实现机制

        对C++ 了解的人都应该知道虚函数(Virtual Function)是通过一张虚函数表(Virtual Table)来实现的。简称为V-Table。 在这个表中,主是要一个类的虚函数的地址表,这张表解决了继承、覆盖的问题,保证其容真实反应实际的函数。这样,在有虚函数的类的实例中这个表被分配在了 这个实例的内存中,所以,当我们用父类的指针来操作一个子类的时候,这张虚函数表就显得由为重要了,它就像一个地图一样,指明了实际所应该调用的函数。

        这里我们着重看一下这张虚函数表。在C++的标准规格说明书中说到,编译器必需要保证虚函数表的指针存在于对象实例中最前面的位置(这是为了保证正确取到虚函数的偏移量)。 这意味着我们通过对象实例的地址得到这张虚函数表,然后就可以遍历其中函数指针,并调用相应的函数。
      
听我扯了那么多,我可以感觉出来你现在可能比以前更加晕头转向了。 没关系,下面就是实际的例子,相信聪明的你一看就明白了。
我们先看一个程序:

typedef void(*Fun)(void);//定义函数指针 返回void 型,并且参数类型是void。
#include <iostream>
#include <string>
using namespace std; 
class Base {	
public:
	
	virtual void f() { cout << "Base::f" << endl; }
	
	virtual void g() { cout << "Base::g" << endl; }
	
	virtual void h() { cout << "Base::h" << endl; }
	
};

class Derived : public Base    
{    
public:
    virtual void f()  { cout << "Derived::f" << endl; }
	virtual void f1() { cout << "Derived::f1" << endl; }
	
	virtual void g1() { cout << "Derived::g1" << endl; }
	
	virtual void h1() { cout << "Derived::h1" << endl; }  
};  
void main(){

	Base b;//基类对象
	Derived d; //子类对象
	Fun pFun = NULL;
	
	cout << "虚函数表地址:" << (int*)(&d) << endl; //应该对象首地址
	
	cout << "虚函数表 — 第一个函数地址:" << (int*)*(int*)(&d) << endl;//虚函数表里存放指针所以 (int*)(&b)加*(间接)

	cout << "调用基类虚函数表里的函数:" <<endl;
	pFun = (Fun)*((int*)*(int*)(&b));//输出的是基类虚函数
	
	cout << "(Fun)*((int*)*(int*)(&b)):";
	pFun();
	
	cout << "(Fun)*((int*)*(int*)(&b)+1):";
	pFun = (Fun)*((int*)*(int*)(&b)+1);//输出的是基类虚函数
	
	pFun();
	
	cout << "调用派生类虚函数表里的函数:" <<endl;
	
	cout << "(Fun)*((int*)*(int*)(&d)):";
	pFun = (Fun)*((int*)*(int*)(&d)); //输出的是子类虚函数
	pFun();
	
	pFun = (Fun)*( (int*)*(int*)(&d)+1 );
	cout << "(Fun)*((int*)*(int*)(&d)+1):"; //输出的是子类虚函数
	pFun();
	
	pFun = (Fun)*( (int*)*(int*)(&d)+2 );
	cout << "(Fun)*((int*)*(int*)(&d)+2):"; //输出的是子类虚函数
	pFun();

	pFun = (Fun)*( (int*)*(int*)(&d)+3 );
	cout << "(Fun)*((int*)*(int*)(&d)+3):";//输出的是子类虚函数
	pFun();

	pFun = (Fun)*( (int*)*(int*)(&d)+4 );
	cout << "(Fun)*((int*)*(int*)(&d)+4):";//输出的是子类虚函数
	pFun();

	pFun = (Fun)*( (int*)*(int*)(&d)+5 );
	cout << "(Fun)*((int*)*(int*)(&d)+5):";//输出的是子类虚函数
	pFun();
}
运行结果如下:
虚函数表地址:0012FF78
虚函数表 — 第一个函数地址:0046C1E4
调用基类虚函数表里的函数:
(Fun)*((int*)*(int*)(&b)):Base::f
(Fun)*((int*)*(int*)(&b)+1):Base::g
调用派生类虚函数表里的函数:
(Fun)*((int*)*(int*)(&d)):Derived::f
(Fun)*((int*)*(int*)(&d)+1):Base::g
(Fun)*((int*)*(int*)(&d)+2):Base::h
(Fun)*((int*)*(int*)(&d)+3):Derived::f1
(Fun)*((int*)*(int*)(&d)+4):Derived::g1
(Fun)*((int*)*(int*)(&d)+5):Derived::h1
Press any key to continue
如运行结果可以看出基类和派生类的虚函数表示例图如下:
基类虚函数:

派生类虚函数:((Fun)*( (int*)*(int*)(&d)+2 )) 从+1,+2,+3,+4,+5,+6 和输出结果可以得出虚函数表结构。

注意:在上面这个图中,我在虚函数表的最后多加了一个结点,这是虚函数表的结束结点,就像字符串的结束符“\0”一样,其标志了虚函数表的结束。这个结束标志的值在不同的编译器下是不同的。在WinXP+VS2003下,这个值是NULL。而在Ubuntu 7.10 + Linux 2.6.22 + GCC 4.1.3下,这个值是如果1,表示还有下一个虚函数表,如果值是0,表示是最后一个虚函数表。

我们可以看到下面几点:
1)虚函数按照其声明顺序放于表中。
2)父类的虚函数在子类的虚函数前面。
我相信聪明的你一定可以参考前面的那个程序,来编写一段程序来验证。
我们从表中可以看到下面几点,
1)覆盖的f()函数被放到了虚表中原来父类虚函数的位置。
2)没有被覆盖的函数依旧。
这样,我们就可以看到对于下面这样的程序,
Base *b = new Derive();
b->f();
由b所指的内存中的虚函数表的f()的位置已经被Derive::f()函数地址所取代,于是在实际调用发生时,是Derive::f()被调用了。这就实现了多态。
下面看看多继承情况,先看一段小程序:
typedef void(*Fun)(void);//定义函数指针 返回void 型,并且参数类型是void。
#include <iostream>
#include <string>
using namespace std; 
class Base {	
public:
	
	virtual void f() { cout << "Base::f" << endl; }
	
	virtual void g() { cout << "Base::g" << endl; }
	
	virtual void h() { cout << "Base::h" << endl; }
	
};

class Base1 {	
public:
	
	virtual void f() { cout << "Base1::f" << endl; }
	
	virtual void g() { cout << "Base1::g" << endl; }
	
	virtual void h() { cout << "Base1::h" << endl; }
	
};

class Base2 {	
public:
	
	virtual void f() { cout << "Base2::f" << endl; }
	
	virtual void g() { cout << "Base2::g" << endl; }
	
	virtual void h() { cout << "Base2::h" << endl; }
	
};

class Derived :public Base,public Base1,public Base2     
{    
public:
    virtual void f()  { cout << "Derived::f" << endl; }
	virtual void f1() { cout << "Derived::f1" << endl; }
	
	virtual void g1() { cout << "Derived::g1" << endl; }
	
};  
void main(){

	Base b;//基类对象
	Derived d; //子类对象
	Fun pFun = NULL;
	int **pVtab=(int **)&d;
	
	cout<<"继承自base的虚函数表"<<endl;

	pFun = (Fun)*( (int*)*(int*)(&d) );
	//pFun = (Fun)*( (int*)*(int*)(&(d+1)) );
	cout << "(Fun)*((int*)*(int*)(&d)):"; //  Derived f被覆盖
	pFun();
	pFun = (Fun)*( (int*)*(int*)(&d)+1 );
	//pFun = (Fun)*( (int*)*(int*)(&(d+1)) );
	cout << "(Fun)*((int*)*(int*)(&d)+1):"; // Base g未被覆盖
	pFun();
	
	pFun = (Fun)*( (int*)*(int*)(&d)+2 );
	cout << "(Fun)*((int*)*(int*)(&d)+2):"; // Base  h 未被覆盖
	pFun();

	pFun = (Fun)*( (int*)*(int*)(&d)+3 ); 
	cout << "(Fun)*((int*)*(int*)(&d)+3):";// Derived f1
	pFun();

	pFun = (Fun)*( (int*)*(int*)(&d)+4 );
	cout << "(Fun)*((int*)*(int*)(&d)+4):";// Derived g1
	pFun();

	//
	cout<<"继承自base1的虚函数表"<<endl;
	cout << "(Fun)*((int*)*(int*)((int *)&d+1)):";
	//pFun = (Fun)*((int*)*(int*)(&d+1)); //这个不正确,对&d+1 之前一定要用(int *)强制转换。
	pFun = (Fun)*((int*)*(int*)((int *)&d+1)); //  Derived f被覆盖
	pFun();
	
	pFun = (Fun)*( (int*)*(int*)((int *)&d+1)+1 );
	cout << "(Fun)*((int*)*(int*)((int *)&d+1)+1):"; //Base1 g
	pFun();
	
	pFun = (Fun)*( (int*)*(int*)((int *)&d+1)+2 );
	cout << "(Fun)*((int*)*(int*)((int *)&d+1)+2):"; //Base1 h
	pFun();
	
	cout<<"继承自base2的虚函数表"<<endl;
	cout << "(Fun)*((int*)*(int*)((int *)&d+2)):";
	//pFun = (Fun)*((int*)*(int*)(&d+1)); //这个不正确,对&d+1 之前一定要用(int *)强制转换。
	pFun = (Fun)*((int*)*(int*)((int *)&d+2)); // Derived f
	pFun();
	
	pFun = (Fun)*( (int*)*(int*)((int *)&d+2)+1 );
	cout << "(Fun)*((int*)*(int*)((int *)&d+2)+1):"; //Base2 g
	pFun();
	
	//pFun = (Fun)*( (int*)*(int*)((int *)&d+2)+2 );//可以
	pFun=(Fun)pVtab[2][2];//int **pVtab 已经两次** 所以前面(Fun)pVtab[2][2]不需要* 了。其实(Fun)*( (int*)*(int*)((int *)&d+2)+2 ) 去掉(int *)也是两个**。
	cout << "(Fun)*((int*)*(int*)((int *)&d+2)+2):"; //Base2 h
	pFun();
}
运行结果如下:
继承自base的虚函数表
(Fun)*((int*)*(int*)(&d)):Derived::f
(Fun)*((int*)*(int*)(&d)+1):Base::g
(Fun)*((int*)*(int*)(&d)+2):Base::h
(Fun)*((int*)*(int*)(&d)+3):Derived::f1
(Fun)*((int*)*(int*)(&d)+4):Derived::g1
继承自base1的虚函数表
(Fun)*((int*)*(int*)((int *)&d+1)):Derived::f
(Fun)*((int*)*(int*)((int *)&d+1)+1):Base1::g
(Fun)*((int*)*(int*)((int *)&d+1)+2):Base1::h
继承自base2的虚函数表
(Fun)*((int*)*(int*)((int *)&d+2)):Derived::f
(Fun)*((int*)*(int*)((int *)&d+2)+1):Base2::g
(Fun)*((int*)*(int*)((int *)&d+2)+2):Base2::h
Press any key to continue
从程序和运行结果看出多继承虚函数表排列如下:


我们可以看见,三个父类虚函数表中的f()的位置被替换成了子类的函数指针。这样,我们就可以任一静态类型的父类来指向子类,并调用子类的f()了。

安全性:
每次写C++的文章,总免不了要批判一下C++。这篇文章也不例外。通过上面的讲述,相信我们对虚函数表有一个比较细致的了解了。水可载舟,亦可覆舟。下面,让我们来看看我们可以用虚函数表来干点什么坏事吧。

一、通过父类型的指针访问子类自己的虚函数

我们知道,子类没有重载父类的虚函数是一件毫无意义的事情。因为多态也是要基于函数重载的。虽然在上面的图中我们可以看到Base1的虚表中有Derive的虚函数,但我们根本不可能使用下面的语句来调用子类的自有虚函数:

Base1 *b1 = new Derive();
b1->f1(); //编译出错。
 b1 显示只能调用 f g h 。
任何妄图使用父类指针想调用子类中的未覆盖父类的成员函数的行为都会被编译器视为非法,所以,这样的程序根本无法编译通过。但在运行时,我们可以通过指针的方式访问虚函数表来达到违反C++语义的行为。看下面补充程序:

Base *b1=new Derived();
	pFun = (Fun)*( (int*)*(int*)(b1)+4 );
	cout << "(Fun)*((int*)*(int*)(b1)+4):";// Derived g1
	pFun();
运行结果为:
(Fun)*((int*)*(int*)(b1)+4):Derived::g1//b1也能调用g1,违反了C++语义行为。

二、访问non-public的虚函数
另外,如果父类的虚函数是private或是protected的,但这些非public的虚函数同样会存在于虚函数表中,所以,我们同样可以使用访问虚函数表的方式来访问这些non-public的虚函数,这是很容易做到的。
看下面程序:

typedef void(*Fun)(void);//定义函数指针 返回void 型,并且参数类型是void。
#include <iostream>
#include <string>
using namespace std; 
class Base {
public:
	
private:
	virtual void f() { cout << "Base::f" << endl; }
};

class Derive : public Base{
	
};

typedef void(*Fun)(void);

void main() {
	Derive d;
	Fun pFun = (Fun)*((int*)*(int*)(&d)+0);
	//d.f();
	pFun();	
}
运行结果为:
Base::f
如果把d.f();去掉,会在编译时就报出 error C2248: 'f' : cannot access private member declared in class 'Base'
总结:
1.
C++是一门Magic的语言,对于程序员来说,我们似乎永远摸不清楚这门语言背着我们在干了什么。需要熟悉这门语言,我们就必需要了解C++里面的那些东西,需要去了解C++中那些危险的东西。不然,这是一种搬起石头砸自己脚的编程语言。
2. C++ Magic 在于指针灵活性,通过指针我们可以做很多事情,也包括很多坏事。

有关C++ 对象内存布局可以阅读  http://blog.csdn.net/haoel/article/details/3081328 (作者分析很到位很精简)。

  • 2
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值