【整理】C++对象内存布局
C++虚函数的作用主要是为了实现多态机制。关于多态,简而言之就是用父类型别的指针指向其子类的实例,然后通过父类的指针调用实际子类的成员函数,这种技术可以让父类的指针有“多种形态”,这是一种泛型技术。所谓泛型技术,说白了就是试图使用不变的代码来实现可变的算法,比如:模板、RTTI、虚函数,要么在编译时决议,要么在运行时决议。
虚函数(Virtual Function)是通过一张虚函数表(Virtual Table)实现的,简称为V-Table,主是要一个类的虚函数的地址,它解决了继承、覆盖的问题,保证其容真实反应实际的函数。在有虚函数的类的实例中这个表被分配在了这个实例的内存中,当用父类指针来操作一个子类时,这张虚函数表就显得由为重要了,它就像一个地图一样,指明了实际所应该调用的函数。
假设有这样的一个类:
class Base
{
public:
virtual void f() { cout << "Base::f" << endl; }
virtual void g() { cout << "Base::g" << endl; }
virtual void h() { cout << "Base::h" << endl; }
};
按照上面的说法,可通过Base的实例来得到虚函数表,下面是实际例程:
typedef void(*Fun)(void);
Fun pFun = NULL;
Base b;
cout << "虚函数表地址:" << (int*)(&b) << endl;
cout << "虚函数表 — 第一个函数地址:" << (int*)*(int*)(&b) << endl;
// Invoke the first virtual function
pFun = (Fun)*((int*)*(int*)(&b));
pFun();
实际运行经果如下:(Windows XP+VS2003, Linux 2.6.22 + GCC 4.1.3)
虚函数表地址:0012FED4
虚函数表 — 第一个函数地址:0044F148
Base::f
通过这个示例可以看出,我们可以通过强行把&b转成int *,取得虚函数表的地址,然后再次取址就可以得到第一个虚函数的地址了,也就是Base::f(),这在上面的程序中得到了验证(把int* 强制转成了函数指针)。通过这个示例,我们就可以知道如果要调用Base::g()和Base::h(),其代码如下:
(Fun)*((int*)*(int*)(&b)+0); // Base::f()
(Fun)*((int*)*(int*)(&b)+1); // Base::g()
(Fun)*((int*)*(int*)(&b)+2); // Base::h()
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;
}
使用虚函数干坏事:(1) 通过父类型的指针访问子类自己的虚函数(见上例多重继承代码):任何妄图使用父类指针调用子类中的未覆盖父类的成员函数的行为都会被编译器视为非法,无法编译通过,但在运行时可通过指针访问虚函数表来达到违反C++语义的行为;(2) 访问non-public的虚函数:如果父类的虚函数是private或是protected的,它们同样会存在于虚函数表中,同样也可使用访问虚函数表的方式来访问这些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); // 访问Base的private函数
pFun();
}
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
博客链接
《C++虚函数表解析》http://blog.csdn.net/haoel/article/details/1948051
《》