浅谈C++虚表指针及虚函数表
众所周知,C++可视作一个语言联邦,而非一个统一的中央帝国。C++不同的语言子集支持不同的编程范式,彼此差异是巨大的,丝毫不亚于两门不同语言的差异。因此首先要明确的是——《当我们谈跑步(C++)时,我们在谈些什么》。
大致说来C++可分为如下几个部分:
1.Better C ——支持过程式编程;
注:具体Better在哪其实本人了解的很有限,今后需要结合《C缺陷和陷阱》来对比着看,进行总结。
2.ADT(抽象数据类型)——提供性能不打折扣的抽象;
注:窃以为这才是C++的卖点,而OOP由于需要一层间接性支持多态会导致内存局部性不够好从而导致性能损失。
3.OOP——支持面向对象程序设计;
注:可继续细分为基于对象OB和面向对象OO,区别在于前者无需继承,后者需要继承来支撑多态性。这里有个关键问题是ADT和OB的区分,有种说法是前者为值语义后者为引用语义。值语义和引用语义的区分个人认为其实很关键,其重要性被忽略了,本人目前理解的也不到位,后面需要进一步加强。
4.Template C++——支持泛型编程(Generic Program)和模板元编程两种范式(后者本人目前还完全不懂:-( )
好了,闲扯到此结束,今天想要讨论的话题是虚表指针及虚函数表,这个当然是属于OOP的范畴。本文计划将按照如下五种情况讨论C++虚表指针及虚函数表的内存布局及其应用:
1.无继承;
2.单继承无覆盖;
3.单继承带覆盖;
4.多继承无覆盖;
5.多继承带覆盖;
另附带一个——
6.C++的黑科技之利用函数指针干坏事。
我们先看第一种情形——
一、无继承时C++中virtual机制的底层实现:
代码如下:
1 #include <iostream> 2 using namespace std; 3 4 class Base{ 5 public: 6 virtual void f(){ cout<<"Base::f"<<endl;} 7 }; 8 9 int main(){ 10 11 typedef void (*Fun)(); 12 Base b1,b2; 13 14 cout<<"基类对象b1的地址: "<<&b1<<endl; 15 cout<<"基类对象b2的地址: "<<&b2<<endl; 16 17 cout<<"基类对象b1的虚表指针地址"<<(int*)(&b1)<<endl; 18 cout<<"基类对象b2的虚表指针地址"<<(int*)(&b2)<<endl; 19 20 cout<<"基类对象b1的虚函数表地址"<<*(int*)(&b1)<<endl; 21 cout<<"基类对象b2的虚函数表地址"<<*(int*)(&b2)<<endl; 22 23 //通过虚函数表vtbl中的存的函数指针来调用基类成员函数 24 Fun pFun = NULL; //初始化为空 25 pFun = (Fun)*( (int*)*(int*)(&b1) + 0 ); 26 pFun(); 27 28 getchar(); 29 return 0; 30 }
运行结果如下:
从运行结果我们可以清楚的看到:
1.所有基类对象共用一个虚函数表vtbl,在内存中地址唯一,仅有一个实例;
2.每个对象在起始处包含一个虚表指针vptr指向这张表,不同对象的虚表指针地址当然是不同的;
3.虚函数表中的每一项存的都是一个函数指针,指向虚函数的实体,可以通过该函数指针来运行该函数。
补充说明:C++中的virtual机制天生就是为了支撑面向对象的多态性而存在的,而多态(运行时)和继承(主要是接口继承)号称面向对象的三大特征之二,其实个人理解这两者是一体而不可分的,不应当是平行地位,而是这两者结合起来和封装构成面向对象的两大支柱。当然封装不仅仅适用于OOP,暂且不谈。
所以在没有继承的情况下谈论virtual机制是很蛋疼的行为,这里只是为了整体完整性,下面我们就将看看——
二、单继承无覆盖时C++中virtual机制的底层实现:
代码如下:
1 #include <iostream> 2 using namespace std; 3 4 class Base{ 5 6 public: 7 virtual void f(){ cout<<"Base::f()"<<endl;} 8 }; 9 10 class Derive:public Base{ 11 12 public: 13 virtual void g(){ cout<<"Derive::g()"<<endl;} 14 virtual void h(){ cout<<"Derive::h()"<<endl;} 15 16 }; 17 18 int main(){ 19 20 typedef void(*Fun)(); 21 Derive d; 22 23 cout<<"派生类对象虚表指针地址: "<<(int*)(&d)<<endl; 24 cout<<"派生类对象虚函数表地址: "<<*(int*)(&d)<<endl; 25 //通过虚函数表项调用虚函数 26 Fun pFun = NULL; 27 pFun = (Fun)*((int*)*(int*)(&d)+0); 28 pFun(); 29 pFun = (Fun)*((int*)*(int*)(&d)+1); 30 pFun(); 31 pFun = (Fun)*((int*)*(int*)(&d)+2); 32 pFun(); 33 34 getchar(); 35 return 0; 36 }
运行结果如下:
从运行结果我们能够推断出派生类对象虚表指针及虚函数表的实现机制,如下图所示:
三、单继承有覆盖时C++中virtual机制的底层实现:
代码如下:
1 #include <iostream> 2 using namespace std; 3 4 class Base{ 5 6 public: 7 virtual void f(){ cout<<"Base::f()"<<endl;} 8 }; 9 10 class Derive:public Base{ 11 12 public: 13 //Override Base::f() ! 14 virtual void f(){ cout<<"Derive::f()"<<endl;} 15 // 16 virtual void g(){ cout<<"Derive::g()"<<endl;} 17 virtual void h(){ cout<<"Derive::h()"<<endl;} 18 19 }; 20 21 int main(){ 22 23 typedef void(*Fun)(); 24 Derive d; 25 26 cout<<"派生类对象虚表指针地址: "<<(int*)(&d)<<endl; 27 cout<<"派生类对象虚函数表地址: "<<*(int*)(&d)<<endl; 28 29 //通过虚函数表项调用虚函数 30 Fun pFun = NULL; 31 pFun = (Fun)*((int*)*(int*)(&d)+0); 32 pFun(); 33 pFun = (Fun)*((int*)*(int*)(&d)+1); 34 pFun(); 35 pFun = (Fun)*((int*)*(int*)(&d)+2); 36 pFun(); 37 38 getchar(); 39 return 0; 40 }
运行结果如下:
从运行结果我们能够推断出派生类对象虚表指针及虚函数表的实现机制,如下图所示:
四、多继承无覆盖时C++中virtual机制的底层实现:
代码如下:
1 #include <iostream> 2 using namespace std; 3 4 class Base1{ 5 6 public: 7 virtual void f(){ cout<<"Base1::f()"<<endl;} 8 virtual void g(){ cout<<"Base1::g()"<<endl;} 9 virtual void h(){ cout<<"Base1::h()"<<endl;} 10 }; 11 12 class Base2{ 13 14 public: 15 virtual void f(){ cout<<"Base2::f()"<<endl;} 16 virtual void g(){ cout<<"Base2::g()"<<endl;} 17 virtual void h(){ cout<<"Base2::h()"<<endl;} 18 }; 19 20 class Base3{ 21 22 public: 23 virtual void f(){ cout<<"Base3::f()"<<endl;} 24 virtual void g(){ cout<<"Base3::g()"<<endl;} 25 virtual void h(){ cout<<"Base3::h()"<<endl;} 26 }; 27 28 29 class Derive:public Base3,public Base2,public Base1{ 30 31 public: 32 virtual void k(){ cout<<"Derive::k()"<<endl;} 33 }; 34 35 int main(){ 36 37 typedef void(*Fun)(); 38 Derive d; 39 40 cout<<"派生类对象虚表指针地址: "<<(int*)(&d)<<endl; 41 cout<<"派生类对象虚函数表地址: "<<*(int*)(&d)<<endl; 42 43 //通过虚函数表项调用虚函数 44 Fun pFun = NULL; 45 46 pFun = (Fun)*((int*)*((int*)(&d) + 0) + 0); 47 pFun(); 48 pFun = (Fun)*((int*)*((int*)(&d) + 0) + 1); 49 pFun(); 50 pFun = (Fun)*((int*)*((int*)(&d) + 0) + 2); 51 pFun(); 52 pFun = (Fun)*((int*)*((int*)(&d) + 0) + 3); 53 pFun(); 54 pFun = (Fun)*((int*)*((int*)(&d) + 1) + 0); 55 pFun(); 56 pFun = (Fun)*((int*)*((int*)(&d) + 1) + 1); 57 pFun(); 58 pFun = (Fun)*((int*)*((int*)(&d) + 1) + 2); 59 pFun(); 60 pFun = (Fun)*((int*)*((int*)(&d) + 2) + 0); 61 pFun(); 62 pFun = (Fun)*((int*)*((int*)(&d) + 2) + 1); 63 pFun(); 64 pFun = (Fun)*((int*)*((int*)(&d) + 2) + 2); 65 pFun(); 66 67 getchar(); 68 return 0; 69 }
运行结果如下:
从运行结果我们能够推断出派生类对象虚表指针及虚函数表的实现机制,如下图所示:
注:多继承情形下派生类有几个父类就会有几个虚函数表,每个虚函数表对应一个基类,且派生类自身独有的、非覆盖基类的虚函数地址放入第一个声明的基类的虚函数表中,但后面我们将看到,纵然如此,想通过使用基类指针调用派生类自身独有的、非覆盖基类的虚函数仍是不被允许的,哪怕你是通过上图Base3的指针调用。
五、多继承有覆盖时C++中virtual机制的底层实现:
代码如下:
1 #include <iostream> 2 using namespace std; 3 4 class Base1{ 5 6 public: 7 virtual void f(){ cout<<"Base1::f()"<<endl;} 8 virtual void g(){ cout<<"Base1::g()"<<endl;} 9 virtual void h(){ cout<<"Base1::h()"<<endl;} 10 }; 11 12 class Base2{ 13 14 public: 15 virtual void f(){ cout<<"Base2::f()"<<endl;} 16 virtual void g(){ cout<<"Base2::g()"<<endl;} 17 virtual void h(){ cout<<"Base2::h()"<<endl;} 18 }; 19 20 class Base3{ 21 22 public: 23 virtual void f(){ cout<<"Base3::f()"<<endl;} 24 virtual void g(){ cout<<"Base3::g()"<<endl;} 25 virtual void h(){ cout<<"Base3::h()"<<endl;} 26 }; 27 28 29 class Derive:public Base3,public Base2,public Base1{ 30 31 public: 32 //Override all base class's f()! 33 virtual void f(){ cout<<"Derive::f()"<<endl;} 34 35 virtual void k(){ cout<<"Derive::k()"<<endl;} 36 }; 37 38 int main(){ 39 40 typedef void(*Fun)(); 41 Derive d; 42 43 cout<<"派生类对象虚表指针地址: "<<(int*)(&d)<<endl; 44 cout<<"派生类对象虚函数表地址: "<<*(int*)(&d)<<endl; 45 46 //1.通过基类指针调用成员函数 47 Base1 *p1 = new Derive(); 48 p1->f(); //派生类重载了的 49 p1->g(); //派生类未重载的 50 p1->h(); //派生类未重载的 51 //p->k(); //这是不被允许的 52 53 Base2 *p2 = new Derive(); 54 p2->f(); //派生类重载了的 55 p2->g(); //派生类未重载的 56 p2->h(); //派生类未重载的 57 //p->k(); //这是不被允许的 58 59 Base3 *p3 = new Derive(); 60 p3->f(); //派生类重载了的 61 p3->g(); //派生类未重载的 62 p3->h(); //派生类未重载的 63 //p->k(); //这是不被允许的 64 65 //2.通过虚函数表项调用虚函数 66 Fun pFun = NULL; 67 68 pFun = (Fun)*((int*)*((int*)(&d) + 0) + 0); 69 pFun(); 70 pFun = (Fun)*((int*)*((int*)(&d) + 0) + 1); 71 pFun(); 72 pFun = (Fun)*((int*)*((int*)(&d) + 0) + 2); 73 pFun(); 74 pFun = (Fun)*((int*)*((int*)(&d) + 0) + 3); 75 pFun(); 76 pFun = (Fun)*((int*)*((int*)(&d) + 1) + 0); 77 pFun(); 78 pFun = (Fun)*((int*)*((int*)(&d) + 1) + 1); 79 pFun(); 80 pFun = (Fun)*((int*)*((int*)(&d) + 1) + 2); 81 pFun(); 82 pFun = (Fun)*((int*)*((int*)(&d) + 2) + 0); 83 pFun(); 84 pFun = (Fun)*((int*)*((int*)(&d) + 2) + 1); 85 pFun(); 86 pFun = (Fun)*((int*)*((int*)(&d) + 2) + 2); 87 pFun(); 88 //测试下这个行不行。果然不行,证明派生类独有的虚函数地址仅仅是放在按声明顺序的第一个基类的虚函数表中。 89 //pFun = (Fun)*((int*)*((int*)(&d) + 2) + 3); 90 //pFun(); 91 92 getchar(); 93 return 0; 94 }
运行结果如下:
从运行结果我们能够推断出派生类对象虚表指针及虚函数表的实现机制,如下图所示:
注:和上一种情形基本类似,区别在于派生类覆盖基类的虚函数地址将会替换原来各个虚函数表中的基类虚函数地址,从而使得多态性得以实现,即能够通过各个基类的指针指向派生类对象,然后通过调用基类中被派生类覆盖重写的虚函数,获得:1.相同的外在使用逻辑,即统一用基类指针指指自身虚函数;2.不同的内在执行逻辑,即执行的是派生类重写了的函数体。
终于快要写完了啊,最后整点趣味性的东东——
六、C++的黑科技之利用函数指针干坏事:
1.类的用户可利用成员函数指针僭越类的作者所设立的访问权限结界;
1 #include <iostream> 2 using namespace std; 3 4 //类的作者 5 class Base{ 6 private: 7 virtual void f(){ cout<<"哥是private的,你们访问不了的"<<endl;} 8 }; 9 10 //类的某不良用户 11 int main(){ 12 13 Base b; 14 //唔,貌似有结界~~~ 15 //b.f(); 16 17 typedef void(*Fun)(); 18 Fun pFun = NULL; 19 pFun = (Fun)*( (int*)*(int*)(&b) + 0 ); 20 pFun(); 21 cout<<"打脸啪啪啪,不要迷信哥,哥只是个传说~"<<endl; 22 23 getchar(); 24 return 0; 25 }
运行结果为:
2.类的用户可通过基类指针访问派生类自身独有的虚函数,前面我们已经看到过,通过常规途径这当然是不被允许的。
1 #include <iostream> 2 using namespace std; 3 4 class Base{ 5 public: 6 virtual void f(){ cout<<"Base::f()"<<endl; } 7 }; 8 9 class Derive:public Base{ 10 public: 11 virtual void g(){ cout<<"想通过基类指针访问哥是不可能滴~~"<<endl; } 12 13 }; 14 15 int main(){ 16 17 //这么干当然是不行滴,编译都不给你通过 18 //Base *p = new Derive(); 19 //p->g(); 20 //但是哥可以这么干 21 Derive d; 22 Base *p = new Derive(); 23 typedef void(*Fun)(); 24 Fun pFun = NULL; 25 pFun = (Fun)*((int*)*(int*)(p) + 1 ); 26 pFun(); 27 cout<<"图样图森破,职业打脸啪啪啪"<<endl; 28 29 getchar(); 30 return 0; 31 }
运行结果为:
注:以上程序均运行于Win7系统32位机 Visual Studio 2010环境下。
全剧终。累~~~