【C++学习】 C++对象的内存布局

xiaoxiao2021-02-28  165

【C++学习】C++ 对象的内存布局

转载至:https://coolshell.cn/articles/12176.html 这篇文章讲解的很透彻,我转载过来对里面的示例进行了一点完善,其中对于虚继承的那块,我觉得作者当时的理解上可能有点偏差,我在这里以我的理解进行修正,也不一定就正确,欢迎大家指出问题,以便以后学习。

上面一篇文章—-【C++学习】C++ 虚函数表解析在谈论虚函数表的时候,至少有以下这些内容没有涉及:

1)有成员变量的情况。

2)有重复继承的情况。

3)有虚拟继承的情况。

4)有钻石型虚拟继承的情况。 这些都是我本篇文章需要向大家说明的东西。所以,这篇文章将会是《【C++学习】C++ 虚函数表解析》的一个续篇,也是一篇高级进阶的文章。我希望大家在读这篇文章之前对C++有一定的基础和了解,并能先读我的上一篇文章。因为这篇文章的深度可能会比较深,而且会比较杂乱,我希望你在读本篇文章时不会有大脑思维紊乱导致大脑死机的情况。;-)

对象的影响因素

简而言之,我们一个类可能会有如下的影响因素:

1)成员变量

2)虚函数(产生虚函数表)

3)单一继承(只继承于一个类)

4)多重继承(继承多个类)

5)重复继承(继承的多个父类中其父类有相同的超类)

6)虚拟继承(使用virtual方式继承,为了保证继承后父类的内存布局只会存在一份)

上述的东西通常是C++这门语言在语义方面对对象内部的影响因素,当然,还会有编译器的影响(比如优化),还有字节对齐的影响。在这里我们都不讨论,我们只讨论C++语言上的影响。 本篇文章着重讨论下述几个情况下的C++对象的内存布局情况。 1)单一的一般继承(带成员变量、虚函数、虚函数覆盖)

2)单一的虚拟继承(带成员变量、虚函数、虚函数覆盖)

3)多重继承(带成员变量、虚函数、虚函数覆盖)

4)重复多重继承(带成员变量、虚函数、虚函数覆盖)

5)钻石型的虚拟多重继承(带成员变量、虚函数、虚函数覆盖)

我们的目标就是,让事情越来越复杂。

单一的一般继承

下面,我们假设有如下所示的一个继承关系: 请注意,在这个继承关系中,父类,子类,孙子类都有自己的一个成员变量。而了类覆盖了父类的f()方法,孙子类覆盖了子类的g_child()及其超类的f()。

我们使用以下程序作为测试程序:(下面程序中,我使用了一个int** pVtab 来作为遍历对象内存布局的指针,这样,我就可以方便地像使用数组一样来遍历所有的成员包括其虚函数表了,在后面的程序中,我也是用这样的方法的,请不必感到奇怪,)

#include <iostream> using namespace std; 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; } }; typedef void(*Fun)(void); int main() { Fun pFun = NULL; GrandChild gc; int** pVtab = (int**)&gc; cout << "[0] GrandChild::_vptr->" << endl; for (int i=0; (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; return 0; }

其运行结果如下所示:(在Win7 VS 2012下) 使用图片表示如下:

可见以下几个方面:

1)虚函数表在最前面的位置。

2)成员变量根据其继承和声明顺序依次放在后面。

3)在单一的继承中,被overwrite的虚函数在虚函数表中得到了更新。

多重继承

下面,再让我们来看看多重继承中的情况,假设有下面这样一个类的继承关系。注意:子类只overwrite了父类的f()函数,而还有一个是自己的函数(我们这样做的目的是为了用g1()作为一个标记来标明子类的虚函数表)。而且每个类中都有一个自己的成员变量:

我们通过下面的程序来查看子类实例的内存布局:下面程序中,父类的成员初始为10,20,30,子类的为100,注意我使用了一个s变量,其中用到了sizof(Base)来找下一个类的偏移量。(因为我声明的是int成员,所以是4个字节,所以没有对齐问题。关于内存的对齐问题,大家可以自行试验,我在这里就不多说了)

#include <iostream> using namespace std; 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; } }; typedef void(*Fun)(void); int main() { Derive d; Fun pFun = NULL; int** pVtab = (int**)&d; cout << "[0] Base1::_vptr->" << endl; for (int i=0; (Fun)pVtab[0][i]!=NULL; i++){ pFun = (Fun)pVtab[0][i]; cout << " ["<<i<<"] "; pFun(); } cout << "[1] Base1.ibase1 = " << (int)pVtab[1] << endl; int s = sizeof(Base1)/4; cout << "[" << s << "] Base2::_vptr->"<<endl; for (int i=0; (Fun)pVtab[s][i]!=NULL; i++){ pFun = (Fun)pVtab[s][i]; cout << " ["<<i<<"] "; pFun(); } cout << "["<< s+1 <<"] Base2.ibase2 = " << (int)pVtab[s+1] << endl; s = s + sizeof(Base2)/4; cout << "[" << s << "] Base3::_vptr->"<<endl; for (int i=0; (Fun)pVtab[4][i]!=NULL; i++){ pFun = (Fun)pVtab[4][i]; cout << " ["<<i<<"] "; pFun(); } cout << "["<< ++s <<"] Base3.ibase3 = " << (int)pVtab[s] << endl; cout << "["<< ++s <<"] Derive.iderive = " << (int)pVtab[s] << endl; return 0; }

其运行结果如下所示:(在Win7 VS 2012下) 使用图片表示是下面这个样子: 我们可以看到:

1) 每个父类都有自己的虚表。

2) 子类的成员函数被放到了第一个父类的表中。

3) 内存布局中,其父类布局依次按声明顺序排列。

4) 每个父类的虚表中的f()函数都被overwrite成了子类的f()。这样做就是为了解决不同的父类类型的指针指向同一个子类实例,而能够调用到实际的函数。

重复继承

下面我们再来看看,发生重复继承的情况。所谓重复继承,也就是某个基类被间接地重复继承了多次。

下图是一个继承图,我们重载了父类的f()函数。 其类继承的源代码如下所示。其中,每个类都有两个变量,一个是整形(4字节),一个是字符(1字节),而且还有自己的虚函数,自己overwrite父类的虚函数。如子类D中,f()覆盖了超类的函数, f1() 和f2() 覆盖了其父类的虚函数,Df()为自己的虚函数。

#include <iostream> using namespace std; class B { public: int ib; char cb; 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;} }; typedef void(*Fun)(void); int main() { D d; Fun pFun = NULL; int** pVtab = (int**)&d; cout << "[0] D::B1::_vptr->" << endl; for (int i=0; (Fun)pVtab[0][i]!=NULL; i++){ pFun = (Fun)pVtab[0][i]; cout << " ["<<i<<"] "; pFun(); } cout << "[1] B.ib = " << (int)pVtab[1] << endl; cout << "[2] B.cb = " << (char)pVtab[2] << endl; int s = sizeof(B)/4; cout << "[" << s << "] B1.ib1 = "<< (int)pVtab[s] <<endl; cout << "[" << ++s << "] B1.cb1 = "<< (char)pVtab[s] <<endl; cout << "=====================================" << endl << endl; s = sizeof(B1)/4; cout << "[" << s << "] D:B2::_vptr->"<<endl; for (int i=0; (Fun)pVtab[s][i]!=NULL; i++){ pFun = (Fun)pVtab[s][i]; cout << " ["<<i<<"] "; pFun(); } cout << "["<< ++s <<"] B.ib = " << (int)pVtab[s] << endl; cout << "["<< ++s <<"] B.cb = " << (char)pVtab[s] << endl; cout << "["<< ++s <<"] B2.ib2 = " << (int)pVtab[s] << endl; cout << "["<< ++s <<"] B2.cb2 = " << (char)pVtab[s] << endl; cout << "=====================================" << endl << endl; s = sizeof(B1)/4 + sizeof(B2)/4; cout << "["<< s <<"] D.id = " << (int)pVtab[s] << endl; cout << "["<< ++s <<"] D.cd = " << (char)pVtab[s] << endl; return 0; }

其运行结果如下所示:(在Win7 VS 2012下)

下面是对于子类实例中的虚函数表的图: 我们可以看见,最顶端的父类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之前,我们先看一看单一虚拟继承的情况。下面是一段在Win7 VS 2012下的测试程序:

#include <iostream> #pragma vtordisp(off) using namespace std; class B { public: int ib; char cb; B():ib(0),cb('B') {} virtual void f() { cout << "B::f()" << endl;} virtual void Bf() { cout << "B::Bf()" << endl;} }; class B1 : virtual public B { public: int ib1; char cb1; public: B1():ib1(11),cb1('1') { ib = 22; cb = 'C'; } virtual void f() { cout << "B1::f()" << endl;} virtual void f1() { cout << "B1::f1()" << endl;} virtual void Bf1() { cout << "B1::Bf1()" << endl;} }; class B2: virtual public B { public: int ib2; char cb2; public: B2():ib2(12),cb2('2') { ib = 23; cb = 'D'; } 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: 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;} }; typedef void(*Fun)(void); int main() { D d; int i = 0; Fun pFun = NULL; int** pVtab = (int**)&d; cout << "sizeof(B) = " << sizeof(B) << endl; cout << "sizeof(B1) = " << sizeof(B1) << endl; cout << "sizeof(B2) = " << sizeof(B2) << endl; cout << "sizeof(D) = " << sizeof(D) << endl; cout << " d.B1::ib = " << d.B1::ib << endl; cout << " d.B1::cb = " << d.B1::cb << endl; cout << "=====================================" << endl << endl; cout << "[0] D::B1::_vfptr->" << endl; for (int i=0; (Fun)pVtab[0][i]!=NULL; i++){ pFun = (Fun)pVtab[0][i]; cout << " ["<<i<<"] "; pFun(); } cout << "[1] D::B1::_vbptr->" << endl; for (i=0; (int *)pVtab[1][i]!=NULL; i++){ cout << " ["<<i<<"] " << " = " << pVtab[1][i] << endl; } int offset1 = pVtab[1][i-1]; cout << "<==== offset1 = " << offset1 << endl; cout << "[2] B1.ib1 = " << (int)pVtab[2] << endl; cout << "[3] B1.cb1 = " << (char)pVtab[3] << endl; cout << "=====================================" << endl << endl; cout << "[4] D::id = " << (int)pVtab[4] << endl; cout << "[5] D::cd = " << (char)pVtab[5] << endl; cout << "[6] D::B1::B::_vfptr->"<<endl; for (int i=0; (Fun)pVtab[6][i]!=NULL; i++){ pFun = (Fun)pVtab[6][i]; cout << " ["<<i<<"] "; pFun(); } cout << "[7] D::B1::B::ib = " << (int)pVtab[7] << endl; cout << "[8] D::B1::B::cb = " << (char)pVtab[8] <<endl; return 0; }

其运行结果如下所示:(在Win7 VS 2012下)

下面的测试程序是看子类D的内存布局,下面是一段在Win7 VS 2012下的测试程序:

#include <iostream> #pragma vtordisp(off) using namespace std; class B { public: int ib; char cb; B():ib(0),cb('B') {} virtual void f() { cout << "B::f()" << endl;} virtual void Bf() { cout << "B::Bf()" << endl;} }; class B1 : virtual public B { public: int ib1; char cb1; public: B1():ib1(11),cb1('1') { ib = 22; cb = 'C'; } virtual void f() { cout << "B1::f()" << endl;} virtual void f1() { cout << "B1::f1()" << endl;} virtual void Bf1() { cout << "B1::Bf1()" << endl;} }; class B2: virtual public B { public: int ib2; char cb2; public: B2():ib2(12),cb2('2') { ib = 23; cb = 'D'; } 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;} }; typedef void(*Fun)(void); int main() { D d; int i = 0; Fun pFun = NULL; int** pVtab = (int**)&d; cout << "sizeof(B) = " << sizeof(B) << endl; cout << "sizeof(B1) = " << sizeof(B1) << endl; cout << "sizeof(B2) = " << sizeof(B2) << endl; cout << "sizeof(D) = " << sizeof(D) << endl; cout << " d.B1::ib = " << d.B1::ib << endl; cout << " d.B1::cb = " << d.B1::cb << endl; cout << " d.B2::ib = " << d.B2::ib << endl; cout << " d.B2::cb = " << d.B2::cb << endl; cout << "=====================================" << endl << endl; cout << "[0] D::B1::_vfptr->" << endl; for (int i=0; (Fun)pVtab[0][i]!=NULL; i++){ pFun = (Fun)pVtab[0][i]; cout << " ["<<i<<"] "; pFun(); } cout << "[1] D::B1::_vbptr->" << endl; for (i=0; (int *)pVtab[1][i]!=NULL; i++){ cout << " ["<<i<<"] " << " = " << pVtab[1][i] << endl; } int offset1 = pVtab[1][i-1]; cout << "<==== offset1 = " << offset1 << endl; cout << "[2] B1.ib1 = " << (int)pVtab[2] << endl; cout << "[3] B1.cb1 = " << (char)pVtab[3] << endl; cout << "=====================================" << endl << endl; cout << "[4] D::B2::_vfptr->"<<endl; for (int i=0; (Fun)pVtab[4][i]!=NULL; i++){ pFun = (Fun)pVtab[4][i]; cout << " ["<<i<<"] "; pFun(); } cout << "[5] D::B1::_vbptr->" << endl; for (i=0; (int *)pVtab[5][i]!=NULL; i++){ cout << " ["<<i<<"] " << " = " << pVtab[5][i] << endl; } int offset2 = pVtab[5][i-1]; cout << "<==== offset2 = " << offset2 << endl; cout << "[6] B2.ib2 = " << (int)pVtab[6] << endl; cout << "[7] B2.cb2 = " << (char)pVtab[7] << endl; cout << "=====================================" << endl << endl; cout << "[8] D::id = " << (int)pVtab[8] << endl; cout << "[9] D::cd = " << (char)pVtab[9] << endl; cout << "[10] D::B1::B::_vfptr->"<<endl; for (int i=0; (Fun)pVtab[10][i]!=NULL; i++){ pFun = (Fun)pVtab[10][i]; cout << " ["<<i<<"] "; pFun(); } cout << "[11] D::B1::B::ib = " << (int)pVtab[11] << endl; //B::ib cout << "[12] D::B1::B::cb = " << (char)pVtab[12] <<endl;//B::cb return 0; }

其运行结果如下所示:(在Win7 VS 2012下)

关于虚拟继承的运行结果我就不画图了,我们直接使用VS2012命令行模式下的一个选项/d1reportSingleClassLayout的打印结果(关于这个选项的具体使用,我后面会有一篇文章专门来描述):

上面两个对虚继承的测试例子中,我们关闭了vtordisp字段的产生,否则在访问B的续表的时候,会出现段错误,具体原因不知道为啥,有大神如果知道可以指导下,更多关于vtordisp字段描述可以参看MSDN的官网—–MSDN。

原作者关于vbptr没有做过多的描述,vbptr指的是虚基类表指针(virtual base table pointer),该指针指向了一个虚表(virtual table),虚表中记录了vbptr与本类的偏移地址;第二项是vbptr到共有基类元素之间的偏移量。在这个例子中,类B1中的vbptr指向了虚表D::$vbtable@B1@,虚表表明公共基类B的vfptr距离类B1的vbptr处的偏移为36,B1的vbptr所在位置为4,偏移36,则B的vfptr应该在40处,跟上图可以对应上,这样就找到了公共基类的vfptr地址,同样,类B2中的vbptr指向了虚表D::$vbtable@B2@,表明公共基类B的vfptr距离类B2的vbptr处的偏移移为20,B2的vbptr所在位置为20,偏移20,则B的vfptr应该在40处,跟上图可以对应上,很明显这两个计算的结果是指向同一个位置,所以最后类中的内容为最后修改的内容,可以查看我上面的测试例子,以及相应的输出,很显然虚继承不用像普通多继承那样维持着公共基类的两份同样的拷贝,节省了存储空间。

转载请注明原文地址: https://www.6miu.com/read-24164.html

最新回复(0)