1)成员变量
2)虚函数(产生虚函数表)
3)单一继承(只继承于一个类)
4)多重继承(继承多个类)
5)重复继承(继承的多个父类中其父类有相同的超类)
6)虚拟继承(使用virtual方式继承,为了保证继承后父类的内存布局只会存在一份)
上述的东西通常是C++这门语言在语义方面对对象内部的影响因素,当然,还会有编译器的影响(比如优化),还有字节对齐的影响。在这里我们都不讨论,我们只讨论C++语言上的影响。
本篇文章着重讨论下述几个情况下的C++对象的内存布局情况。
1)单一的一般继承(带成员变量、虚函数、虚函数覆盖)
2)单一的虚拟继承(带成员变量、虚函数、虚函数覆盖)
3)多重继承(带成员变量、虚函数、虚函数覆盖)
4)重复多重继承(带成员变量、虚函数、虚函数覆盖)
5)钻石型的虚拟多重继承(带成员变量、虚函数、虚函数覆盖)
我们的目标就是,让事情越来越复杂。
简单地复习一下,我们可以通过对象的地址来取得虚函数表的地址
可以参考上一篇:c++虚函数表解析
我们同样可以用这种方式来取得整个对象实例的内存布局。因为这些东西在内存中都是连续分布的,我们只需要使用适当的地址偏移量,我们就可以获得整个内存对象的布局。
下面,我们假设有如下所示的一个继承关系:
请注意,在这个继承关系中,父类,子类,孙子类都有自己的一个成员变量。而了类覆盖了父类的f()方法,孙子类覆盖了子类的g_child()及其超类的f()。
我们的源程序如下所示:
/* * ===================================================================================== * * Filename: parent.cpp * Description: c++ 对象的内存布局单一继承测试 * Created: 2013年11月25日 19时20分02秒 * Revision: none * Compiler: gcc * Author: max_min_ * ===================================================================================== */ #include <iostream> using namespace std; class CParent { public: CParent():iparent(10) { } virtual void f() { cout << "CParent::f()" << endl; } virtual void g() { cout << "CParent::g()" << endl; } virtual void h() { cout << "CParent::h()" << endl; } public: int iparent; }; class CChild:public CParent { public: CChild():ichild(100) { } virtual void f() { cout << "CChild::f()" << endl; } virtual void g_child() { cout << "CChild::g_child()" << endl; } virtual void h_child() { cout << "CChild::h_child()" << endl; } public: int ichild; }; class CGrandchild:public CChild { public: CGrandchild():igrandchild(1000) { } virtual void f() { cout << "CGrandchild::f()" << endl; } virtual void g_child() { cout << "CGrandchild::g_child()" << endl; } virtual void h_grandchild() { cout << "CGrandchild::h_grandchild()" << endl; } public: int igrandchild; }; typedef void (*CallbackFunc)(void ); // Function pointer int main(void) { CallbackFunc pFunc = NULL; CGrandchild m_gc; int **pVtab = (int **)&m_gc; cout << "[0] CGrandchild::_vptr->" << endl; for(int i= 0; (CallbackFunc)pVtab[0][i] != NULL ; i++) { pFunc = (CallbackFunc)pVtab[0][i]; cout << " [" << i << "] "; pFunc(); } cout << endl; cout << "[1] parent-iparen = " << (int) pVtab[1] << endl; cout << "[2] child-ichild = " << (int) pVtab[2] << endl; cout << "[3] grandchild-igrandchild = " << (int) pVtab[3] << endl; return 0; }
其运行结果如下所示:(gcc version 4.4.3 (Ubuntu 4.4.3-4ubuntu5.1) )
[0] CGrandchild::_vptr-> [0] CGrandchild::f() [1] CParent::g() [2] CParent::h() [3] CGrandchild::g_child() [4] CChild::h_child() [5] CGrandchild::h_grandchild() [1] parent-iparen = 10 [2] child-ichild = 100 [3] grandchild-igrandchild = 1000
使用图片表示如下:
可见以下几个方面:
1)虚函数表在最前面的位置。
2)成员变量根据其继承和声明顺序依次放在后面。
3)在单一的继承中,被overwrite的虚函数在虚函数表中得到了更新
下面,再让我们来看看多重继承中的情况,假设有下面这样一个类的继承关系。注意:子类只overwrite了父类的f()函数,而还有一个是自己的函数(我们这样做的目的是为了用g1()作为一个标记来标明子类的虚函数表)。而且每个类中都有一个自己的成员变量:
我们的类继承的源代码如下所示:父类的成员初始为10,20,30,子类的为100
我们通过下面的程序来查看子类实例的内存布局:下面程序中,注意我使用了一个s变量,其中用到了sizof(Base)来找下一个类的偏移量。(因为我声明的是int成员,所以是4个字节,所以没有对齐问题。关于内存的对齐问题,大家可以自行试验,我在这里就不多说了)源程序如下:
/* * ===================================================================================== * * Filename: mulparent.cpp * Description: c++ 对象的内存布局多重继承测试 * Version: 1.0 * Created: 2013年11月25日 19时58分54秒 * Revision: none * Compiler: gcc * Author: max_mix_ * ===================================================================================== */ #include <iostream> using namespace std; class Base1 { public: Base1():ibase1(10) { } virtual void f() { cout << "Base1::f()" << endl; } virtual void g() { cout << "Base1::g()" << endl; } virtual void h() { cout << "Base1::h()" << endl; } public: int ibase1; }; class Base2 { public: Base2():ibase2(20) { } virtual void f() { cout << "Base2::f()" << endl; } virtual void g() { cout << "Base2::g()" << endl; } virtual void h() { cout << "Base2::h()" << endl; } public: int ibase2; }; class Base3 { public: Base3():ibase3(30) { } virtual void f() { cout << "Base3::f()" << endl; } virtual void g() { cout << "Base3::g()" << endl; } virtual void h() { cout << "Base3::h()" << endl; } public: int ibase3; }; class Derive:public Base1, public Base2, public Base3 { public: Derive():iderive(100) { } virtual void f() { cout << "Derive::f()" << endl; } virtual void g1() { cout << "Derive::g1()" << endl; } public: int iderive; }; typedef void(*Fun)(void); //Function pointer int main(void ) { Derive d; Fun pFun = NULL; int** pVtab = (int**)&d; cout << "[0] Base1::_vptr->" << endl; pFun = (Fun)pVtab[0][0]; cout << " [0] "; pFun(); pFun = (Fun)pVtab[0][1]; cout << " [1] "; pFun(); pFun = (Fun)pVtab[0][2]; cout << " [2] "; pFun(); pFun = (Fun)pVtab[0][3]; cout << " [3] "; pFun(); pFun = (Fun)pVtab[0][4]; cout << " [4] "; cout<<pFun<<endl; cout << "[1] Base1::ibae1 = " << (int )pVtab[1] << endl; int s = sizeof(Base1)/4; cout << "["<< s << "] Base2::_vptr-> " << endl; pFun = (Fun)pVtab[s][0]; cout << " [0] "; pFun(); pFun = (Fun)pVtab[s][1]; cout << " [1] "; pFun(); pFun = (Fun)pVtab[s][2]; cout << " [2] "; pFun(); pFun = (Fun)pVtab[s][3]; cout << " [3] "; cout<<pFun<<endl; cout << "["<< s+1 <<"] Base2.ibase2 = " << (int)pVtab[s+1] << endl; s = s + sizeof(Base2)/4; cout << "[" << s << "] Base3::_vptr->"<<endl; pFun = (Fun)pVtab[s][0]; cout << " [0] "; pFun(); pFun = (Fun)pVtab[s][1]; cout << " [1] "; pFun(); pFun = (Fun)pVtab[s][2]; cout << " [2] "; pFun(); pFun = (Fun)pVtab[s][3]; cout << " [3] "; cout<<pFun<<endl; s++; cout << "["<< s <<"] Base3.ibase3 = " << (int)pVtab[s] << endl; s++; cout << "["<< s <<"] Derive.iderive = " << (int)pVtab[s] << endl; }其运行结果如下所示:
[0] Base1::_vptr-> [0] Derive::f() [1] Base1::g() [2] Base1::h() [3] Derive::g1() [4] 1 //注意:windows下 这里是0 [1] Base1::ibae1 = 10 [2] Base2::_vptr-> [0] Derive::f() [1] Base2::g() [2] Base2::h() [3] 1 // 注意:windows下 这里是0 [3] Base2.ibase2 = 20 [4] Base3::_vptr-> [0] Derive::f() [1] Base3::g() [2] Base3::h() [3] 0 [5] Base3.ibase3 = 30 [6] Derive.iderive = 100使用图片表示是下面这个样子:
我们可以看到:
1) 每个父类都有自己的虚表。
2) 子类的成员函数被放到了第一个父类的表中。
3) 内存布局中,其父类布局依次按声明顺序排列。
4) 每个父类的虚表中的f()函数都被overwrite成了子类的f()。这样做就是为了解决不同的父类类型的指针指向同一个子类实例,而能够调用到实际的函数。
本文核心资料来自:http://blog.csdn.net/haoel/article/details/3081328