C++ Data Member内存布局

    添加时间:2013-7-8 点击量:

    若是一个类只定义了类名,没定义任何办法和字段,如class A{};那么class A的每个实例占用1个字节的内存,编译器会会在这个其实例中安插一个char,以包管每个A实例在内存中有独一的地址,如A a,b;&a!=&b。若是一个直接或是间接的持续(不是虚持续)了多个类,若是这个类及其父类像A一样没有办法没有字段,那么这个类的每个实例的大小都是1字节,若是有虚持续,那就不是1字节了,每虚持续一个类,这个类的实例就会多一个指向被虚持续父类的指针。还有一点值得申明的就是像A如许的类,编译器不必然会产生传说中的那6个办法,这些办法只会在须要的时辰产生,如class  A没有被任何处所应用那这些办法编译器就没有须要产生,若是这个类实例化了,那么会产生default constructor,而destructor则不必然产生。


    若是一个类中有static data member,nonstatic data member,还有const data member,enum,那么它的内存布局会是什么样的呢,看下面简单的类Point:



    class Point
    
    {
    public:
    Point():maxCount(
    10){}
    private:
    int X;
    static int count;
    int Y;
    const int maxCount ;
    enum{
    minCount
    =2
    };
    };


    Sizeof(Point)=12,为什么占12字节呢,我信赖很多人都知道是哪几个成员变量占用的,就是X,Y,maxCount,maxCount作为常量字段,但在Point的每个实例中可能有不合的值,当然属于Point实例的一项目组,若是把maxCount定义成static,那它就不不是Point实例的一项目组了,若是定义成static  const int maxCount=1;则maxCount分派在.data段中,若是没有初始化则分派在.bss段中,反正跟Point的实例无关,count分派在.bss段中,minCount分派在.rdata段中,总之count,maxCount,minCount在编译连接完成之后,内存(虚拟地址)就分派好了,在法度加载的时辰,会把他们的虚拟地址对应上实际的物理地址。


    Data member的内存布局:nonstatic data member在class object中的次序和其申明的次序一样,static data  member和const member不在class object中因为他们只有一份,被class object共享,所以static data member和const data member,列举并不会响应class object的大小。关于段的信息,我感觉是每个C/C++法度员必须知道的。而Point每次实例化的时辰则只须要分派X,Y,maxCount须要的内存。


    每个类的data member在内存中应当是连气儿的,若是呈现数据对齐的景象,可能中心会有空白地带。请看下面几个类:




    class AA
    
    {
    protected:
    int X;
    char a;
    };

    class BB:public AA
    {
    protected:
    char b;
    };

    class CC:public BB
    {
    protected:
    char c;
    };


    Sizeof(AA)=8//对齐3字节


    Sizeof(BB)=12//两个3字节对齐


    Sizeof(CC)=16//编译器“无耻”的用了3个3字节对齐



    关于内存对齐题目我信赖大师经常碰到,我就不空话,我之前写过一篇关于内存对齐的文章《数据对齐》


    编译器为什么要无耻的在class CC中加3个3字节对齐呢,如许每个CC的实例就大了9字节。若是编译器不加这9字节的空白,那么CC的每个实例就是8字节,前面的X占4字节,后面的a,b,c占3字节,加1字节的空白对齐,正好8字节,没有谁很傻很天真的认为好是占7字节吧。


    若是CC占用8字节内存,同样的AA,BB都是8字节的内存,如许的话,若是把一个指向AA实例的指针赋给一个指向CC实例的指针,那么就会把AA中的8字节直接盖到CC的8字节上,成果CC实例中的b,c都被赋上了不是我们想要的值,这很可能会导致你的法度出题目。


    父类的data member会在子类的实例中有完全的一份,如许在有持续关系的类之间进行类型转换,就只用简单的批改指针的指向。


    Data Member的存取。对一个data member的存取,编译器把对象实例的肇端地址加上data member的偏移量。如CC c;


    c.X=1;相当于&c+(&CC::X-1),减一其实是为了区分是指向object的指针还是指向data member的指针,指向data member的要减一。每一个data member的偏移量在编译的时辰是知道的,按照成员变量的类型和内存对齐,存在virtual持续或是虚办法的景象编译器会主动加上一些帮助的指针,如指向虚办法的指针,指向虚持续父类的指针等。


    在data member的存取效力上,struct member 、class member、单一持续或是多重持续的景象下效力都是一样的,因为他们的存储其实都是&obj+(&class.datamember-1)。在虚持续的景象下,可能会影响存储机能,如经由过程一个指针来存取一个指向虚持续而来的data member,那么机能会有影响,因为在虚持续的时辰,在编译的时辰还不克不及断定这个data member是来自子类还是父类,只有在运行的时辰才干揣度出来,其实就是多了一步指针的操纵,在虚持续中,若是是经由过程对象实例来操纵虚持续而来的data member,则不会有任何机能题目,因为不存在什么多态性,所有器材在编译的时辰内存地址都断定了。


    虚持续还是虚办法为了实现多态一样,多了一步,若是不须要多态,而是经由过程对象实例调用相干的办法就不会有机能题目。

    无论对感情还是对生活,“只要甜不要苦”都是任性而孩子气的,因为我们也不完美,我们也会伤害人。正因为我们都不完美,也因为生活从不是事事如意,所以对这些“瑕疵”的收纳才让我们对生活、对他人的爱变得日益真实而具体。—— 汪冰《世界再亏欠你,也要敢于拥抱幸福》
    分享到: