c++虚函数表
编译环境:windows 10 64bit + VS2015
1、虚函数表简介
c++中虚函数的主要作用是实现多态机制,父类可以通过指针调用子类成员函数。多态机制为一种泛型技术,比如模板技术、RTTI技术,虚函数技术,有的是编译时确定调用方式,有的是运行时确定调用方式。
在c++中,虚函数通过虚函数表来实现,简称V-Table。V-Table中存放类的虚函数地址,它解决了继承、覆盖等问题,保证其内容真实反映实际的函数。在有虚函数类的实例中,V-Table被分配在此类实例的内存中。当使用父类指针来操作子类时,可通过V-Table找到实际应该被调用的函数。
一般情况下,为保证C++高性能(特别是在多层继承或多重继承的情况下),编译器一般将虚函数表指针存放在对象实例的最前面位置。虚函数在类例实中布局如下图:
所以在有虚函数的类中,对象的首地址存放的是v-ptr指针地址。以Base类为例进行说明:
代码:
class Base { public: virtual void f() { cout << "Base::f()" << endl; } virtual void g() { cout << "Base::g()" << endl; } virtual void h() { cout << "Base::h()" << endl; } }; int main() { typedef void(*Fun)(void); Base b; Fun pFun = nullptr; cout << "虚函数表地址:" << (int*)(&b) << endl; cout << "虚函数表 -> 第1个函数指针地址:" << (int*)*(int*)(&b)+0 << endl; // 调用第1个虚函数 pFun = (Fun)*((int*)*(int*)(&b)); cout << "虚函数表 -> 第1个函数地址:" << (int*)pFun << endl; pFun(); // 调用第2个虚函数 pFun = (Fun)*((int*)*(int*)(&b) + 1); cout << "虚函数表 -> 第2个函指针数地址:" << (int*)*(int*)(&b)+1 << endl; cout << "虚函数表 -> 第2个函数地址:" << (int*)pFun << endl; pFun(); // 调用第3个虚函数 pFun = (Fun)*((int*)*(int*)(&b) + 2); cout << "虚函数表 -> 第3个函数指针地址:" << (int*)*(int*)(&b)+2 << endl; cout << "虚函数表 -> 第3个函数地址:" << (int*)pFun << endl; pFun(); return 0; }
示例中通过把&b强行转换为int*,取得虚函数表的地址为0x00eff724,虚函数表0x00eff724中存放的是指向虚函数指针的第一个地址0x0131a030。如下图所示:
我们可以通过如下代码在虚函数表中依次取得指向虚函数指针地址和虚函数地址。
(int*)*(int*)(&b)+0 // Base::f() (int*)*(int*)(&b)+1 // Base::g() (int*)*(int*)(&b)+2 // Base::h()
从程序输出和内存中的内容显示表明,确实如此。调用一个函数Base::f()时,函数指针pFun指向地址0x0131a030存放的地址0x013114ec,而0x013114ec为函数Base::f()代码存放的起始地址,此时执行pFun时,输出“Base::f()”。
因此,虚函数表与类对象执行的关系示意图可以表示如下:
在虚函数表的最后多加了一个结点,这是虚函数表的结束结点,就像字符串的结束符“/0”一样,其标志了虚函数表的结束。这个结束标志的值在不同的编译器下是不同的。在windows10 + VS2015下,这个值是NULL。而在Ubuntu 7.10 + Linux 2.6.22 + GCC 4.1.3下,这个值是如果1,表示还有下一个虚函数表,如果值是0,表示是最后一个虚函数表。
2、无覆盖和有覆盖虚函数表区别
下面分别说明“无覆盖”和“有覆盖”时的虚函数表的样子。没有覆盖父类的虚函数是毫无意义的。之所以要讲述没有覆盖的情况,主要目的是进行比较。在比较之下,可更加清楚地了解内部的具体实现。
1) 一般继承(无虚函数覆盖)
在这个继承关系中,子类没有重载任何父类的函数。那么,在派生类的实例中,其虚函数表如下所示:
可以看到下面几点:
- 虚函数按照其声明顺序放于表中。
- 父类的虚函数在子类的虚函数前面。
2)一般继承(有虚函数覆盖)
覆盖父类的虚函数是很显然的事情,不然,虚函数就变得毫无意义。如果子类中有虚函数重载了父类的虚函数,继承关系如下:
在这个类的设计中,只覆盖了父类的一个函数:f()。对于派生类的实例,其虚函数表结构如下:
可以看到如下几点:
- 覆盖的f()函数被放到了虚表中原来父类虚函数的位置。
- 没有被覆盖的函数依旧。
因此,对于下面的程序:
Base *b = new Derive(); b->f();
由于Base类型指针b所指向的内存中虚函数表的f()位置被Derive::f()函数的地址所取代,于是实际调用发生时,就Derive::f()被调用了,如此实现了多态。
在单重继承情况下,子类与父类共用一个虚函数表。
所以下列代码,实例d的大小仍然为4字节。
class BaseA { public: virtual void f() { cout << "Base::f()" << endl; } virtual void g() { cout << "Base::g()" << endl; } virtual void h() { cout << "Base::h()" << endl; } }; class BaseB :public BaseA { public: virtual void j() { cout << "BaseB::j()" << endl; } virtual void k() { cout << "BaseB::k()" << endl; } virtual void l() { cout << "BaseB::l()" << endl; } }; class BaseC:public BaseB { public: virtual void c1() { cout << "BaseC::c1()" << endl; } virtual void c2() { cout << "BaseC::c2()" << endl; } virtual void c3() { cout << "BaseC::c3()" << endl; } }; class Derive :public BaseC { public: virtual void f1() { cout << "Derive::f1()" << endl; } virtual void g1() { cout << "Derive::g1()" << endl; } virtual void h1() { cout << "Derive::h1()" << endl; } }; typedef void(*Fun)(void); int main() { Derive d; cout << sizeof(d) << endl; //输出4 说明单重继承共一个虚函数表。 return 0; }
3)多重继承(无虚函数覆盖)
多重继承中的情况,如下图类的继承关系(子类并没有覆盖父类的函数)
其对应子类实例中虚函数表结构如下:
可以看到:
- 每个父类都有自己的虚表。
- 子类的成员函数被放到了第一个父类的表中。(所谓的第一个父类是按照声明顺序来判断的)
解决不同父类型指针指向同一个实例时,而能调用到实际的函数。
在多重继承下,继承了多少个基类,就会产生多少个虚函数表。
所以下列代码中,d的大小为12字节。
class BaseA { public: virtual void f() { cout << "BaseA::f()" << endl; } virtual void g() { cout << "BaseA::g()" << endl; } virtual void h() { cout << "BaseA::h()" << endl; } }; class BaseB { public: virtual void j() { cout << "BaseB::j()" << endl; } virtual void k() { cout << "BaseB::k()" << endl; } virtual void l() { cout << "BaseB::l()" << endl; } }; class BaseC { public: virtual void c1() { cout << "BaseC::c1()" << endl; } virtual void c2() { cout << "BaseC::c2()" << endl; } virtual void c3() { cout << "BaseC::c3()" << endl; } }; class Derive :public BaseA,public BaseB,public BaseC { public: virtual void f1() { cout << "Derive::f1()" << endl; } virtual void g1() { cout << "Derive::g1()" << endl; } virtual void h1() { cout << "Derive::h1()" << endl; } }; typedef void(*Fun)(void); int main() { Derive d; cout << sizeof(d) << endl; //输出12。 int* pVtable1 = (int*)(&d); cout << "第1个虚函数表地址:" << pVtable1 << endl; Fun pf =(Fun)(*((int*)*pVtable1)); cout << "BaseA::f()虚函数表地址:" << (int*)((int*)*pVtable1) << endl; cout << "BaseA::f()地址:" << (int*)*((int*)*pVtable1) << endl; cout << "BaseA::f()地址:" << pf << endl; pf(); Fun pg = (Fun)(*(((int*)*pVtable1) + 1) ); cout << "BaseA::g()虚函数表地址:" << (int*)(((int*)*pVtable1) + 1) << endl; cout << "BaseA::g()地址:" << pg << endl; pg(); Fun ph = (Fun)(*((int*)*pVtable1 + 2)); cout << "BaseA::h()虚函数表地址:" << (int*)(((int*)*pVtable1) + 2) << endl; cout << "BaseA::h()地址:" << pf << endl; ph(); Fun f1 = (Fun)(*((int*)*pVtable1 + 3)); cout << "Derive::f1()虚函数表地址:" << (int*)(((int*)*pVtable1) + 3) << endl; cout << "Derive::f1()地址:" << f1 << endl; f1(); int * pVtable2 = pVtable1 + 1; cout << "第2个虚函数表地址:" << pVtable2 << endl; Fun pj = (Fun)(*((int*)*pVtable2 + 0)); cout << "BaseB::j()虚函数表地址:" << (int*)((int*)*pVtable2) << endl; cout << "BaseB::j()地址:" << (int*)*((int*)*pVtable2) << endl; cout << "BaseB::j()地址:" << pj << endl; pj(); Fun pk = (Fun)(*(((int*)*pVtable2) + 1)); cout << "BaseB::k()虚函数表地址:" << (int*)(((int*)*pVtable2) + 1) << endl; cout << "BaseB::k()地址:" << pk << endl; pk(); Fun pl = (Fun)(*(((int*)*pVtable2) + 2)); cout << "BaseB::l()虚函数表地址:" << (int*)(((int*)*pVtable2) + 2) << endl; cout << "BaseB::l()地址:" << pl << endl; pl(); int * pVtable3 = pVtable1 + 2; cout << "第3个虚函数表地址:" << pVtable3 << endl; Fun c1 = (Fun)(*((int*)*pVtable3 + 0)); cout << "BaseC::c1()虚函数表地址:" << (int*)((int*)*pVtable3) << endl; cout << "BaseC::c1()地址:" << (int*)*((int*)*pVtable3) << endl; cout << "BaseC::c1()地址:" << c1 << endl; c1(); Fun c2 = (Fun)(*(((int*)*pVtable3) + 1)); cout << "BaseC::c2()虚函数表地址:" << (int*)(((int*)*pVtable3) + 1) << endl; cout << "BaseC::c2()地址:" << c2 << endl; c2(); Fun c3 = (Fun)(*(((int*)*pVtable3) + 2)); cout << "BaseC::c3()虚函数表地址:" << (int*)(((int*)*pVtable3) + 2) << endl; cout << "BaseC::c3()地址:" << c3 << endl; c3(); return 0; }
输出以及内存布局如下图所示:
证明前文所述虚函数表和虚函数地址生成规则是正确的。
3)多层继承(有虚函数覆盖)
类继承关系如下:
子类实例中虚函数表结构:
可以看见,三个父类虚函数表中的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()
3、安全性
1) 通过父类型的指针访问子类自己的虚函数。
我们知道,子类没有重载父类的虚函数是一件毫无意义的事情。因为多态也是要基于函数重载的。虽然在上面的图中我们可以看到Base1的虚表中有Derive的虚函数,但我们根本不可能使用下面的语句来调用子类的自有虚函数:
Base1 *b1 = new Derive(); b1->g1(); //编译出错
任何妄图使用父类指针想调用子类中的未覆盖父类的成员函数的行为都会被编译器视为非法,所以,这样的程序根本无法编译通过。但在运行时,我们可以通过指针的方式访问虚函数表来达到违反C++语义的行为。
2)访问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(); }
4、关于多重继承的虚函数访问程序
#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; }