网站前端程序制作开发,哪个网站可以做电视背景墙,网站建设违约,徐州网站建设技术外包来源#xff1a;http://blog.csdn.net/haoel/article/details/3081328 前言 07年12月#xff0c;我写了一篇《C虚函数表解析》的文章#xff0c;引起了大家的兴趣。有很多朋友对我的文章留了言#xff0c;有鼓励我的#xff0c;有批评我的#xff0c;还有很多问问题的。…来源http://blog.csdn.net/haoel/article/details/3081328 前言 07年12月我写了一篇《C虚函数表解析》的文章引起了大家的兴趣。有很多朋友对我的文章留了言有鼓励我的有批评我的还有很多问问题的。我在这里一并对大家的留言表示感谢。这也是我为什么再写一篇续言的原因。因为在上一篇文章中我用了的示例都是非常简单的主要是为了说明一些机理上的问题也是为了图一些表达上方便和简单。不想这篇文章成为了打开C对象模型内存布局的一个引子引发了大家对C对象的更深层次的讨论。当然我之前的文章还有很多方面没有涉及从我个人感觉下来在谈论虚函数表里至少有以下这些内容没有涉及
1有成员变量的情况。
2有重复继承的情况。
3有虚拟继承的情况。
4有钻石型虚拟继承的情况。
这些都是我本篇文章需要向大家说明的东西。所以这篇文章将会是《C虚函数表解析》的一个续篇也是一篇高级进阶的文章。我希望大家在读这篇文章之前对C有一定的基础和了解并能先读我的上一篇文章。因为这篇文章的深度可能会比较深而且会比较杂乱我希望你在读本篇文章时不会有大脑思维紊乱导致大脑死机的情况。;-)
对象的影响因素
简而言之我们一个类可能会有如下的影响因素
1成员变量
2虚函数产生虚函数表
3单一继承只继承于一个类
4多重继承继承多个类
5重复继承继承的多个父类中其父类有相同的超类
6虚拟继承使用virtual方式继承为了保证继承后父类的内存布局只会存在一份
上述的东西通常是C这门语言在语义方面对对象内部的影响因素当然还会有编译器的影响比如优化还有字节对齐的影响。在这里我们都不讨论我们只讨论C语言上的影响。
本篇文章着重讨论下述几个情况下的C对象的内存布局情况。
1单一的一般继承带成员变量、虚函数、虚函数覆盖
2单一的虚拟继承带成员变量、虚函数、虚函数覆盖
3多重继承带成员变量、虚函数、虚函数覆盖
4重复多重继承带成员变量、虚函数、虚函数覆盖
5钻石型的虚拟多重继承带成员变量、虚函数、虚函数覆盖
我们的目标就是让事情越来越复杂。
知识复习
我们简单地复习一下我们可以通过对象的地址来取得虚函数表的地址如
typedef void(*Fun)(void);Base b;Fun pFun NULL;cout 虚函数表地址 (int*)(b) endl;
cout 虚函数表 — 第一个函数地址 (int*)*(int*)(b) endl;// Invoke the first virtual function
pFun (Fun)*((int*)*(int*)(b));
pFun();
我们同样可以用这种方式来取得整个对象实例的内存布局。因为这些东西在内存中都是连续分布的我们只需要使用适当的地址偏移量我们就可以获得整个内存对象的布局。
本篇文章中的例程或内存布局主要使用如下编译器和系统
1Windows XP 和 VC 2003
2Cygwin 和 G 3.4.4
单一的一般继承
下面我们假设有如下所示的一个继承关系 注意在这个继承关系中父类子类孙子类都有自己的一个成员变量。而子类覆盖了父类的f()方法孙子类覆盖子类的g_child()及其超类的f()。
我们的源程序如下所示 class Parent {
public:int iparent;Parent ():iparent (10) {}virtual void f() { cout Parent::f() endl; }virtual void g() { cout Parent::g() endl; }virtual void h() { cout Parent::h() endl; }};class Child : public Parent {
public:int ichild;Child():ichild(100) {}virtual void f() { cout Child::f() endl; }virtual void g_child() { cout Child::g_child() endl; }virtual void h_child() { cout Child::h_child() endl; }
};class GrandChild : public Child{
public:int igrandchild;GrandChild():igrandchild(1000) {}virtual void f() { cout GrandChild::f() endl; }virtual void g_child() { cout GrandChild::g_child() endl; }virtual void h_grandchild() { cout GrandChild::h_grandchild() endl; }
};
我们使用以下程序作为测试程序下面程序中我使用了一个
int** pVtab
来作为遍历对象内存布局的指针这样我就可以方便地像使用数组一样来遍历所有的成员包括其虚函数表了在后面的程序中我也是用这样的方法的请不必感到奇怪typedef void(*Fun)(void);GrandChild gc;int** pVtab (int**)gc;cout [0] GrandChild::_vptr- endl;
for (int i0; (Fun)pVtab[0][i]!NULL; i){pFun (Fun)pVtab[0][i];cout [i] ;pFun();
}
cout [1] Parent.iparent (int)pVtab[1] endl;
cout [2] Child.ichild (int)pVtab[2] endl;
cout [3] GrandChild.igrandchild (int)pVtab[3] endl;
其运行结果如下所示在
VC 2003
和
G 3.4.4
下[0] GrandChild::_vptr- [0] GrandChild::f() [1] Parent::g() [2] Parent::h() [3] GrandChild::g_child() [4] Child::h1() [5] GrandChild::h_grandchild() [1] Parent.iparent 10 [2] Child.ichild 100 [3] GrandChild.igrandchild 1000 使用图片表示如下可见以下几个方面
1虚函数表在最前面的位置。
2成员变量根据其继承和声明顺序依次放在后面。
3在单一的继承中被overwrite的虚函数在虚函数表中得到了更新。 多重继承 下面再让我们来看看多重继承中的情况假设有下面这样一个类的继承关系。注意子类只overwrite了父类的f()函数而还有一个是自己的函数我们这样做的目的是为了用g1()作为一个标记来标明子类的虚函数表。而且每个类中都有一个自己的成员变量 我们的类继承的源代码如下所示父类的成员初始为102030子类的为100 class Base1 {
public:int ibase1;Base1():ibase1(10) {}virtual void f() { cout Base1::f() endl; }virtual void g() { cout Base1::g() endl; }virtual void h() { cout Base1::h() endl; }};class Base2 {
public:int ibase2;Base2():ibase2(20) {}virtual void f() { cout Base2::f() endl; }virtual void g() { cout Base2::g() endl; }virtual void h() { cout Base2::h() endl; }
};class Base3 {
public:int ibase3;Base3():ibase3(30) {}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:int iderive;Derive():iderive(100) {}virtual void f() { cout Derive::f() endl; }virtual void g1() { cout Derive::g1() endl; }
};
查看子类实例的内存布局下面程序中注意我使用了一个
s
变量其中用到了
sizof(Base)
来找下一个类的偏移量。因为我声明的是
int
成员所以是
4
个字节所以没有对齐问题。关于内存的对齐问题大家可以自行试验我在这里就不多说了typedef void(*Fun)(void);Derive d;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] ; coutpFunendl;cout [1] Base1.ibase1 (int)pVtab[1] endl;int s sizeof(Base1)/4;cout [ s ] Base2::_vptr-endl;
pFun (Fun)pVtab[s][0];
cout [0] ; pFun();Fun (Fun)pVtab[s][1];
cout [1] ; pFun();pFun (Fun)pVtab[s][2];
cout [2] ; pFun();pFun (Fun)pVtab[s][3];
out [3] ;
coutpFunendl;cout [ s1 ] Base2.ibase2 (int)pVtab[s1] 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] ;
coutpFunendl;s;
cout [ s ] Base3.ibase3 (int)pVtab[s] endl;
s;
cout [ s ] Derive.iderive (int)pVtab[s] endl;
其运行结果如下所示在
VC 2003
和
G 3.4.4
下[0] Base1::_vptr- [0] Derive::f() [1] Base1::g() [2] Base1::h() [3] Driver::g1() [4] 00000000 ç 注意在GCC下这里是1 [1] Base1.ibase1 10 [2] Base2::_vptr- [0] Derive::f() [1] Base2::g() [2] Base2::h() [3] 00000000 ç 注意在GCC下这里是1 [3] Base2.ibase2 20 [4] Base3::_vptr- [0] Derive::f() [1] Base3::g() [2] Base3::h() [3] 00000000 [5] Base3.ibase3 30 [6] Derive.iderive 100
使用图片表示是下面这个样子我们可以看到
1 每个父类都有自己的虚表。
2 子类的成员函数被放到了第一个父类的表中。
3 内存布局中其父类布局依次按声明顺序排列。
4 每个父类的虚表中的f()函数都被overwrite成了子类的f()。解决了不同的父类类型的指针指向同一个子类实例而能够调用到实际的函数。
重复继承
下面我们再来看看发生重复继承的情况。所谓重复继承也就是某个基类被间接地重复继承了多次。
下图是一个继承图我们重载了父类的f()函数。 其类继承的源代码如下所示。其中每个类都有两个变量一个是整形4字节一个是字符1字节而且还有自己的虚函数自己overwrite父类的虚函数。如子类D中f()覆盖了超类的函数 f1() 和f2() 覆盖了其父类的虚函数Df()为自己的虚函数。 class B
{
public:int ib;char cb;
public:B():ib(0),cb(B) {}virtual void f() { cout B::f() endl;}virtual void Bf() { cout B::Bf() endl;}
};
class B1 : public B
{
public:int ib1;char cb1;
public:B1():ib1(11),cb1(1) {}virtual void f() { cout B1::f() endl;}virtual void f1() { cout B1::f1() endl;}virtual void Bf1() { cout B1::Bf1() endl;}};
class B2: public B
{
public:int ib2;char cb2;
public:B2():ib2(12),cb2(2) {}virtual void f() { cout B2::f() endl;}virtual void f2() { cout B2::f2() endl;}virtual void Bf2() { cout B2::Bf2() endl;}};class D : public B1, public B2
{
public:int id;char cd;
public:D():id(100),cd(D) {}virtual void f() { cout D::f() endl;}virtual void f1() { cout D::f1() endl;}virtual void f2() { cout D::f2() endl;}virtual void Df() { cout D::Df() endl;}};
我们用来存取子类内存布局的代码如下所示在
VC 2003
和
G 3.4.4
下
typedef void(*Fun)(void);
int** pVtab NULL;
Fun pFun NULL;D d;
pVtab (int**)d;
cout [0] D::B1::_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] ; pFun();
pFun (Fun)pVtab[0][5];
cout [5] 0x pFun endl;cout [1] B::ib (int)pVtab[1] endl;
cout [2] B::cb (char)pVtab[2] endl;
cout [3] B1::ib1 (int)pVtab[3] endl;
cout [4] B1::cb1 (char)pVtab[4] endl;cout [5] D::B2::_vptr- endl;
pFun (Fun)pVtab[5][0];
cout [0] ; pFun();
pFun (Fun)pVtab[5][1];
cout [1] ; pFun();
pFun (Fun)pVtab[5][2];
cout [2] ; pFun();
pFun (Fun)pVtab[5][3];
cout [3] ; pFun();
pFun (Fun)pVtab[5][4];
cout [4] 0x pFun endl;cout [6] B::ib (int)pVtab[6] endl;
cout [7] B::cb (char)pVtab[7] endl;
cout [8] B2::ib2 (int)pVtab[8] endl;
cout [9] B2::cb2 (char)pVtab[9] endl;cout [10] D::id (int)pVtab[10] endl;
cout [11] D::cd (char)pVtab[11] endl;程序运行结果如下 GCC 3.4.4 VC 2003 [0] D::B1::_vptr- [0] D::f() [1] B::Bf() [2] D::f1() [3] B1::Bf1() [4] D::f2() [5] 0x1 [1] B::ib 0 [2] B::cb B [3] B1::ib1 11 [4] B1::cb1 1 [5] D::B2::_vptr- [0] D::f() [1] B::Bf() [2] D::f2() [3] B2::Bf2() [4] 0x0 [6] B::ib 0 [7] B::cb B [8] B2::ib2 12 [9] B2::cb2 2 [10] D::id 100 [11] D::cd D [0] D::B1::_vptr- [0] D::f() [1] B::Bf() [2] D::f1() [3] B1::Bf1() [4] D::Df() [5] 0x00000000 [1] B::ib 0 [2] B::cb B [3] B1::ib1 11 [4] B1::cb1 1 [5] D::B2::_vptr- [0] D::f() [1] B::Bf() [2] D::f2() [3] B2::Bf2() [4] 0x00000000 [6] B::ib 0 [7] B::cb B [8] B2::ib2 12 [9] B2::cb2 2 [10] D::id 100 [11] D::cd D
下面是对于子类实例中的虚函数表的图 我们可以看见最顶端的父类B其成员变量存在于B1和B2中并被D给继承下去了。而在D中其有B1和B2的实例于是B的成员在D的实例中存在两份一份是B1继承而来的另一份是B2继承而来的。所以如果我们使用以下语句则会产生二义性编译错误
D d;
d.ib 0; //二义性错误
d.B1::ib 1; //正确
d.B2::ib 2; //正确
注意上面例程中的最后两条语句存取的是两个变量。虽然我们消除了二义性的编译错误但B类在D中还是有两个实例这种继承造成了数据的重复我们叫这种继承为重复继承。重复的基类数据成员可能并不是我们想要的。所以C引入了虚基类的概念。
钻石型多重虚拟继承
虚拟继承的出现就是为了解决重复继承中多个间接父类的问题的。钻石型的结构是其最经典的结构。也是我们在这里要讨论的结构
上述的“重复继承”只需要把B1和B2继承B的语法中加上virtual 关键就成了虚拟继承其继承图如下所示 上图和前面的“重复继承”中的类的内部数据和接口都是完全一样的只是我们采用了虚拟继承其省略后的源码如下所示class B {……};
class B1 : virtual public B{……};
class B2: virtual public B{……};
class D : public B1, public B2{ …… };
在查看
D
之前我们先看一看单一虚拟继承的情况。下面是一段在
VC2003
下的测试程序因为
VC
和
GCC
的内存而局上有一些细节上的不同所以这里只给出
VC
的程序
GCC
下的程序大家可以根据我给出的程序自己仿照着写一个去试一试int** pVtab NULL;
Fun pFun NULL;B1 bb1;pVtab (int**)bb1;
cout [0] B1::_vptr- endl;
pFun (Fun)pVtab[0][0];
cout [0] ;
pFun(); //B1::f1();
cout [1] ;
pFun (Fun)pVtab[0][1];
pFun(); //B1::bf1();
cout [2] ;
cout pVtab[0][2] endl;cout [1] 0x;
cout (int*)*((int*)(bb1)1) endl; //B1::ib1
cout [2] B1::ib1 ;
cout (int)*((int*)(bb1)2) endl; //B1::ib1
cout [3] B1::cb1 ;
cout (char)*((int*)(bb1)3) endl; //B1::cb1cout [4] 0x;
cout (int*)*((int*)(bb1)4) endl; //NULLcout [5] B::_vptr- endl;
pFun (Fun)pVtab[5][0];
cout [0] ;
pFun(); //B1::f();
pFun (Fun)pVtab[5][1];
cout [1] ;
pFun(); //B::Bf();
cout [2] ;
cout 0x (Fun)pVtab[5][2] endl;cout [6] B::ib ;
cout (int)*((int*)(bb1)6) endl; //B::ib
cout [7] B::cb ;其运行结果如下我结出了GCC的和VC2003的对比 GCC 3.4.4 VC 2003 [0] B1::_vptr - [0] : B1::f() [1] : B1::f1() [2] : B1::Bf1() [3] : 0 [1] B1::ib1 : 11 [2] B1::cb1 : 1 [3] B::_vptr - [0] : B1::f() [1] : B::Bf() [2] : 0 [4] B::ib : 0 [5] B::cb : B [6] NULL : 0 [0] B1::_vptr- [0] B1::f1() [1] B1::Bf1() [2] 0 [1] 0x00454310 ç该地址取值后是-4 [2] B1::ib1 11 [3] B1::cb1 1 [4] 0x00000000 [5] B::_vptr- [0] B1::f() [1] B::Bf() [2] 0x00000000 [6] B::ib 0 [7] B::cb B 这里大家可以自己对比一下。关于细节上我会在后面一并再说。
下面的测试程序是看子类D的内存布局同样是VC 2003的因为VC和GCC的内存布局上有一些细节上的不同而VC的相对要清楚很多所以这里只给出VC的程序GCC下的程序大家可以根据我给出的程序自己仿照着写一个去试一试
D d;pVtab (int**)d;
cout [0] D::B1::_vptr- endl;
pFun (Fun)pVtab[0][0];
cout [0] ; pFun(); //D::f1();
pFun (Fun)pVtab[0][1];
cout [1] ; pFun(); //B1::Bf1();
pFun (Fun)pVtab[0][2];
cout [2] ; pFun(); //D::Df();
pFun (Fun)pVtab[0][3];
cout [3] ;
cout pFun endl;//cout pVtab[4][2] endl;
cout [1] 0x;
cout (int*)((dd)1) endl; //????cout [2] B1::ib1 ;
cout *((int*)(dd)2) endl; //B1::ib1
cout [3] B1::cb1 ;
cout (char)*((int*)(dd)3) endl; //B1::cb1//---------------------
cout [4] D::B2::_vptr- endl;
pFun (Fun)pVtab[4][0];
cout [0] ; pFun(); //D::f2();
pFun (Fun)pVtab[4][1];
cout [1] ; pFun(); //B2::Bf2();
pFun (Fun)pVtab[4][2];
cout [2] ;
cout pFun endl;cout [5] 0x;
cout *((int*)(dd)5) endl; // ???cout [6] B2::ib2 ;
cout (int)*((int*)(dd)6) endl; //B2::ib2
cout [7] B2::cb2 ;
cout (char)*((int*)(dd)7) endl; //B2::cb2cout [8] D::id ;
cout *((int*)(dd)8) endl; //D::id
cout [9] D::cd ;
cout (char)*((int*)(dd)9) endl;//D::cdcout [10] 0x;
cout (int*)*((int*)(dd)10) endl;
//---------------------
cout [11] D::B::_vptr- endl;
pFun (Fun)pVtab[11][0];
cout [0] ; pFun(); //D::f();
pFun (Fun)pVtab[11][1];
cout [1] ; pFun(); //B::Bf();
pFun (Fun)pVtab[11][2];
cout [2] ;
cout pFun endl;cout [12] B::ib ;
cout *((int*)(dd)12) endl; //B::ib
cout [13] B::cb ;
cout (char)*((int*)(dd)13) endl;//B::cb
下面给出运行后的结果分VC和GCC两部份 GCC 3.4.4 VC 2003 [0] B1::_vptr - [0] : D::f() [1] : D::f1() [2] : B1::Bf1() [3] : D::f2() [4] : D::Df() [5] : 1 [1] B1::ib1 : 11 [2] B1::cb1 : 1 [3] B2::_vptr - [0] : D::f() [1] : D::f2() [2] : B2::Bf2() [3] : 0 [4] B2::ib2 : 12 [5] B2::cb2 : 2 [6] D::id : 100 [7] D::cd : D [8] B::_vptr - [0] : D::f() [1] : B::Bf() [2] : 0 [9] B::ib : 0 [10] B::cb : B [11] NULL : 0 [0] D::B1::_vptr- [0] D::f1() [1] B1::Bf1() [2] D::Df() [3] 00000000 [1] 0x0013FDC4 ç 该地址取值后是-4 [2] B1::ib1 11 [3] B1::cb1 1 [4] D::B2::_vptr- [0] D::f2() [1] B2::Bf2() [2] 00000000 [5] 0x4539260 ç 该地址取值后是-4 [6] B2::ib2 12 [7] B2::cb2 2 [8] D::id 100 [9] D::cd D [10] 0x00000000 [11] D::B::_vptr- [0] D::f() [1] B::Bf() [2] 00000000 [12] B::ib 0 [13] B::cb B
关于虚拟继承的运行结果我就不画图了前面的作图已经让我产生了很严重的厌倦感所以就偷个懒了大家见谅了
在上面的输出结果中我用不同的颜色做了一些标明。我们可以看到如下的几点
1无论是GCC还是VC除了一些细节上的不同其大体上的对象布局是一样的。也就是说先是B1黄色然后是B2绿色接着是D灰色而B这个超类青蓝色的实例都放在最后的位置。
2关于虚函数表尤其是第一个虚表GCC和VC有很重大的不一样。但仔细看下来还是VC的虚表比较清晰和有逻辑性。
3VC和GCC都把B这个超类放到了最后而VC有一个NULL分隔符把B和B1和B2的布局分开。GCC则没有。
4VC中的内存布局有两个地址我有些不是很明白在其中我用红色标出了。取其内容是-4。接道理来说这个指针应该是指向B类实例的内存地址这个做法就是为了保证重复的父类只有一个实例的技术。但取值后却不是。这点我目前还并不太清楚还向大家请教。
5GCC的内存布局中在B1和B2中则没有指向B的指针。这点可以理解编译器可以通过计算B1和B2的size而得出B的偏移量。