今天读了coolshell博主几年前的旧文,C++虚函数表解析,代码如下:
#pragma once #include <iostream> 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;} private: virtual void p(){cout<<"Base::p(private)"<<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 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 (*Func)(void); int main() { Base b; Func pFun = NULL; cout<<"虚函数表的地址:"<<(int*)(&b)<<" "<<(void*)(&b)<<endl; cout<<"虚函数表中第一个函数的位置:"<<(int*)*reinterpret_cast<int*>(&b)<< " "<<(int*)*(int*)(&b)<<endl; pFun = (Func)*((int*)*(int*)(&b)); //Base::f() pFun(); pFun = (Func)*((int*)*(int*)(&b) + 1); pFun(); pFun = (Func)*((int*)*(int*)(&b) + 2); pFun(); pFun = (Func)*((int*)*(int*)(&b) + 3); //通过虚函数表调用父类private虚函数 pFun(); Derive d; int** pVtab = (int**)&d; //Base1's vtable //pFun = (Fun)*((int*)*(int*)((int*)&d+0)+0); pFun = (Func)pVtab[0][0]; pFun(); //pFun = (Fun)*((int*)*(int*)((int*)&d+0)+1); pFun = (Func)pVtab[0][1]; pFun(); //pFun = (Fun)*((int*)*(int*)((int*)&d+0)+2); pFun = (Func)pVtab[0][2]; pFun(); //Derive's vtable //pFun = (Fun)*((int*)*(int*)((int*)&d+0)+3); pFun = (Func)pVtab[0][3]; pFun(); //The tail of the vtable pFun = (Func)pVtab[0][4]; cout<<pFun<<endl; //Base2's vtable //pFun = (Fun)*((int*)*(int*)((int*)&d+1)+0); pFun = (Func)pVtab[1][0]; pFun(); //pFun = (Fun)*((int*)*(int*)((int*)&d+1)+1); pFun = (Func)pVtab[1][1]; pFun(); pFun = (Func)pVtab[1][2]; pFun(); //The tail of the vtable pFun = (Func)pVtab[1][3]; cout<<pFun<<endl; //Base3's vtable //pFun = (Fun)*((int*)*(int*)((int*)&d+1)+0); pFun = (Func)pVtab[2][0]; pFun(); //pFun = (Fun)*((int*)*(int*)((int*)&d+1)+1); pFun = (Func)pVtab[2][1]; pFun(); pFun = (Func)pVtab[2][2]; pFun(); //The tail of the vtable pFun = (Func)pVtab[2][3]; cout<<pFun<<endl; system("pause"); return 0; }
下面是一点感悟:
1.为什么只能通过强行把&b转成int *,取得虚函数表的地址。转换成(char*)或者(void*)就没法正确获取地址?
比如这样:(int*)*(char*)(&b) 或者(char*)*(char*)(&b)。(void*)*(int*)(&b),(char*)*(int*)(&b)可获得正确的结果。
突然意识到这也算问题?在32位的编译器设置下指针占用的地址为4byte和int类型一样,*(T*)(&b)只要获取一个产度为4byte的值作为函数指针的地址即可,也就是说T的长度需要和指针产度一致才能获取正确的地址,例如:(long*)*(long*)(&b),也可获取虚函数表的地址。
2.虚函数表的结构:
i.一般继承(无虚函数覆盖):虚函数表类似一个一维数组的链式结构
表中存的是虚函数的地址:
获取虚函数表的地址:(int*)(&b)
获取虚函数表中第一个函数的地址:(int*)*(虚函数表的地址 + 0)
第二个函数的地址:(int*)*(虚函数表的地址 + 1)
ii.多重继承的时候虚函数表的结构类似如一个二维数组,每一行对应一个基类的虚函数序列:
int** pVtab = (int**)(&derive);