C++ Data 内存布局

本文探讨了C++中类的内存布局,包括数据成员的位置、继承的影响、虚拟继承的作用等。介绍了编译器如何处理非静态数据成员、虚拟函数表、对齐方式以及静态成员的存储。特别强调了虚拟继承可能导致的效率问题和内存开销,以及编译器如何通过优化技术来减轻这些影响。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

 

       class X{};

       class Y:virtual public X{};

       class Z:virtual public X{};

       class B:virtual public X{};

       class A:public Y,public Z,public B{};

       class M{};

       class N:virtual public X,virtual public M

       {

       };

       int main()

       {

          cout<<sizeof(X)<<endl;

          cout<<sizeof(Y)<<endl;

          cout<<sizeof(Z)<<endl;

          cout<<sizeof(A)<<endl;

          cout<<endl<<endl<<sizeof(N)<<endl;

          return 0;

       }

       Code::Block     1  4   4   12   4

       VS2008         1  4   4   12   8

 

       C++ Standard并不强制规定如“base class subobjects的排列次序”,或“不同存取层级的data member的排列次序”。

       C++对象模型尽量以空间优化和存取速度优化的考虑来表现nonstaticdata members,并且保持和C语言struct数据配置的兼容性。它把数据直接存放在每一个class object之中。对于继承而来的nonstatic data members也是如此。不过并没有强制定义其间的排列次序。

       每一个class object因此必须有足够的大小以容纳它所有的nonstatic data members。因为它可能比你想象的还大。

       1,由编译器自动加上的额外data members,用以支持某些语言特性(主要是各种virtual 特性)。

       2,因为alignment(边界调整)的需要。

 

        对member functions本身的分析,会直到整个class的声明都出现了才开始。因此,在一个inline member function躯体之内的一个datamember绑定操作,会在整个class声明完成之后才发生。

       然而,这对于member function的argument list并不为真。Argemment list中的名称还是会在它们第一次遭遇时被适当地决议完成。

       typedef intlength;

       class Point

       {

       public:

           void fun(length a)

           {

              cout<<typeid(a).name()<<endl;      //i

              cout<<typeid(_a).name()<<endl;        //f

           }

           typedef float length;

           length _a;

       };


DataMemberr的布局

       C++ Standard 要求,在同一个access section中,members的排列只需符合“较晚出现的members在class object中有较高的地址”这一条件即可,也就是说,各个members并不一定得连续排列,相对次序不会改变,什么东西可能会介于被声明的members之间呢?members的边界调整(alignment)可能 就需要填补一些bytes。编译器还可能会合成一些内部使用的data members,以支持整个对象模型。

       C++ Standard秉持先前所说的“对于内存所持的放任态度”。

       C++ Standard也允许编译器将多个access section之中的data members自由排列,不必在乎它们出现在class声明中的次序。

       每一个static data member只有一个实体,存放在程序的data segment之中。

       若取一个static data member的地址,会得到一个指向其数据类型的指针。

       如果有两个classes,每一个都声明了一个static member freelist,那么当它们都被放在程序的data segment时,就会导致名称冲突。编译器的解决方法是暗中对每一个static data member编码,名字重组(name-mangleing),以获得一个独一无二的程序识别代码。

       Nonstatic datamembers直接存放在每一个classobject之中。除非经由明确的或暗喻的classobject,没有办法直接存取它们。

       欲对一个nonstatic data member进行存取操作,编译器需要把class object的起始地址加上data member的偏移量(offset)。

       无虚拟继承,每一个nonstatic data member的偏移量(offset)在编译时期即可获知,甚至如果member属于一个base class subobject(派生自单一或多重继承串链)也是一样。因此,存取一个nonstaticdata member,其效率和存取一个Cstruct member或一个nonderivedclass的member是一样的。

       其继承结构中有一个virtual base class,并且被存取的member是一个从该virtualbase class继承而来的member时,就会有重大的差异。这个时候我们不能够说pt指向哪一种class type(因此我们也就不知道编译时期这个member真正的offset位置),所以这个存取操作必须延迟至执行期,以由一个额外的间接导引完成,才能够解决。

 

 

 

 

在C++继承模型中,一个derivedclass object所表现出来的东西,是其自己的members加上base classes members的总和,至于derived class members和base classes members的排列次序并未在C++ Standard中强制指定。理论上,编译器可以自由安排之。

class Point2d

{

protected:

      float x,y;

}pt2d;

 

class Point3d

{

protected:

      float z;

}pt3d;

 

 

C++保证“出现 在derived class中的base class subobject有其完整原样性”。

 

 

只加上单一继承

class Concrete1

{

private:

    int val;

    char bit1;

};

class Concrete2:public Concrete1

{

private:

    char bit2;

};

class Concrete3:public Concrete2

{

private:

    char bit3;

};

 

 

 

 

 

加上多态(Adding Polymorphism)

 

class Point2d

{

public:

    Point2d(float x = 0.0,floaty = 0.0)

        :_x(x),_y(y){}

    virtualfloatz() const {return 0.0;}

    virtualvoidz(float) {}

    virtualconstPoint2d &operator +=(const Point2d& rhs)

    {

        cout<<"Point2d::+="<<endl;

        _x += rhs._x;

        _y += rhs._y;

        return *this;

    }

protected:

    float _x,_y;

};

 

 

1,导入一个和Point2d有关的virtual table,用来存放它所声明的每一个virtual functions的地址。这个table的元素数目一般而言是被声明的virtual functions的数目,再加上一个或两个slots(用以支持runtime type identification)。

 

2,在每一个class object导入一个vptr,提供执行期的链接,使每一个object能够找到相应的virtual table

 

3,加强constructor,使它能够为vptr设定初值,让它能够指向class所对应的virtual table。这可能意味着在derived class和每一个base class的constructor中,重新设定vptr值。

 

4,加强destructor,使它能够抹消“指向class相关virtual table”的vptr。

 

class Point3d:public Point2d

{

public:

    Point3d(float x = 0.0,floaty = 0.0,float z = 0.0)

        :Point2d(x,y),_z(z){}

    virtualfloatz() const {return _z;}

    virtualvoidz(float newZ) {_z = newZ;}

    virtual Point3d &operator +=(constPoint2d& rhs)

    {

        cout<<"Point3d::+="<<endl;

        Point2d::operator+=(rhs);

        _z += rhs.z();

        return *this;

    }

protected:

    float _z;

};

 

 

 

 

多重继承(Multiple Inheritance)

 

     多重继承的复杂度在于derived class和其上一个base class乃至上上一个base class之间的“非自然关系”。

 

class Point2d

{

public:

   virtual~Point2d(){}

protected:

   float_x,_y;

};

 

class Point3d:public Point2d

{

public:

   virtual~Point3d(){}

protected:

   float_z;

};

 

class Vertex

{

public:

   virtual~Vertex(){}

protected:

   Vertex *next;

};

 

class Vertex3d:public Point3d,publicVertex

{

public:

   virtual~Vertex3d(){}

protected:

   floatmumble;

};

 

对于一个多重派生对象,将其地址指定给“最左端(也就是第一个)baseclass的指针”,情况将和单一继承时相同,因为二者都指向指针相同的起始地址。至于第二个或后继的base class的地址指定操作,则需要将地址修改过:加上(或减去,如果downcase的话)介于中间的base class subobject(s) 的大小。

 

 

 

某些编译器设计有一种优化技术,只要第二个(或后继)base class声明了一个virtual function,而第一个base class没有,就把多个base classes的次序进行调换,这样可以在derived class object中少产生一个vptr。(Code::Block和VS2008能够做到)

 

 

 

虚拟继承(Virtual Inheritance)

    

     一个不变局和一个共享局部。不变局部中的数据,不管后继如何衍化,总是拥有固定的offset,所以这一部分可以被直接存取,至于共享局部,所表现的就是virtual base class subobject。这一部分的数据,其位置会因为每次的派生操作而有所变化,所以它们只可以被间接存取。

 

 

在每一个derived class object中安插一些指针,每个指针指向一个virtual base class。要存取继承得来的virtual base members,可以使用相关指针间接完成。

 

 

每一个对象必须针对其每一个virtual base class背负一个额外的指针。然而理想上我们却希望class object有固定的负担,不因为其virtual base classes的数目而有所变化。

 

Microsoft编译器(VS2008就是这样做的)引入所谓所谓的virtual base classes。每一个class object如果有一个或多个virtual base classes,就会由编译器安插一个指针,指向virtual base class table。至于真正的virtual base class指针,当然是被放在该表格中。另一个方法(Code::Block这样做的),是在virtual function table中放置virtual base class 的offset。

 

 

 

        一般而言,virtual base class最有效的一种运用形式就是:一个抽象的virtual base class,没有任何data member。

 

 

 

对象成员的效率(ObjectMember Efficiency)

    

    封装是不会带来执行期的效率成本,使用inline存取函数亦然。

 

    单一继承一般不会影响测试的效率,因为members被连续存储于derived class object中,并且其offset在编译时期就己知了。

 

    虚拟继承的效率令人失望!编译器对虚基类数据的操作,产生间接存取操作,间接性压抑把所有的运算都移往缓存器执行的优化能力。但是间接性并不会严重影响非程序的执行效率。虚拟继承所带来的主要冲击是,它妨碍了优化的有效性。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值