C++ 类中有虚函数(虚函数表)时 内存分布详解

2020-01-06 16:40:20王冬梅

这样做就是为了解决不同的父类类型的指针指向同一个子类实例,而能够调用到实际的函数。

多重继承(有虚函数覆盖)
下面我们再来看看,如果发生虚函数覆盖的情况。

下图中,我们在子类中覆盖了父类的f()函数。

下面是对于子类实例中的虚函数表的图:

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

 


Derive d;

      Base1 *b1 = &d;

      Base2 *b2 = &d;

      Base3 *b3 = &d;

      b1->f(); //Derive::f()

      b2->f(); //Derive::f()

      b3->f(); //Derive::f()

 

      b1->g(); //Base1::g()

      b2->g(); //Base2::g()

      b3->g(); //Base3::g()

 

安全性

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

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

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

Base1 *b1 = new Derive();

b1->f1(); //编译出错

任何妄图使用父类指针想调用子类中的未覆盖父类的成员函数的行为都会被编译器视为非法,所以,这样的程序根本无法编译通过。但在运行时,我们可以通过指针的方式访问虚函数表来达到违反C++语义的行为。(关于这方面的尝试,通过阅读后面附录的代码,相信你可以做到这一点)

二、访问non-public的虚函数

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

如:


class Base {

  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);

  pFun();

}

附录一:VC中查看虚函数表

我们可以在VC的IDE环境中的Debug状态下展开类的实例就可以看到虚函数表了(并不是很完整的)

附录 二:例程

下面是一个关于多重继承的虚函数表访问的例程:


#include <iostream>

using namespace std;

 

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 Base3 {

public:

      virtual void f() { cout << "Base3::f" << endl; }

      virtual void g() { cout << "Base3::g" << endl; }

      virtual void h() { cout << "Base3::h" << endl; }

};

 

 

class Derive : public Base1, public Base2, public Base3 {

public:

      virtual void f() { cout << "Derive::f" << endl; }

      virtual void g1() { cout << "Derive::g1" << endl; }

};

 

 

typedef void(*Fun)(void);

 

int main()

{

      Fun pFun = NULL;

 

      Derive d;

      int** pVtab = (int**)&d;

 

      //Base1's vtable

      //pFun = (Fun)*((int*)*(int*)((int*)&d+0)+0);

      pFun = (Fun)pVtab[0][0];

      pFun();

 

      //pFun = (Fun)*((int*)*(int*)((int*)&d+0)+1);

      pFun = (Fun)pVtab[0][1];

      pFun();

 

      //pFun = (Fun)*((int*)*(int*)((int*)&d+0)+2);

      pFun = (Fun)pVtab[0][2];

      pFun();

 

      //Derive's vtable

      //pFun = (Fun)*((int*)*(int*)((int*)&d+0)+3);

      pFun = (Fun)pVtab[0][3];

      pFun();

 

      //The tail of the vtable

      pFun = (Fun)pVtab[0][4];

      cout<<pFun<<endl;

 

 

      //Base2's vtable

      //pFun = (Fun)*((int*)*(int*)((int*)&d+1)+0);

      pFun = (Fun)pVtab[1][0];

      pFun();

 

      //pFun = (Fun)*((int*)*(int*)((int*)&d+1)+1);

      pFun = (Fun)pVtab[1][1];

      pFun();

 

      pFun = (Fun)pVtab[1][2];

      pFun();

 

      //The tail of the vtable

      pFun = (Fun)pVtab[1][3];

      cout<<pFun<<endl;

 

      //Base3's vtable

      //pFun = (Fun)*((int*)*(int*)((int*)&d+1)+0);

      pFun = (Fun)pVtab[2][0];

      pFun();

 

      //pFun = (Fun)*((int*)*(int*)((int*)&d+1)+1);

      pFun = (Fun)pVtab[2][1];

      pFun();

 

      pFun = (Fun)pVtab[2][2];

      pFun();

 

      //The tail of the vtable

      pFun = (Fun)pVtab[2][3];

      cout<<pFun<<endl;

 

      return 0;

}