对象的影响因素
简而言之,我们一个类可能会有如下的影响因素:
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