美文网首页
类内成员变量的读取与布局

类内成员变量的读取与布局

作者: 404Not_Found | 来源:发表于2021-08-29 23:43 被阅读0次
    • 作者: 雪山肥鱼
    • 时间:20210829 11:00
    • 目的: 类对象所占空间

    静态成员属于类

    class Myacls
    {
    public:
        int m_i;
        static int m_si;
        int m_j;
        static int m_sj;//声明
    
        int m_k;
        static int m_sk;
    public:
    
    };
    
    int Myacls::m_sj = 0;//定义,存在数据段中
    
    int main(int argc, char **argv)
    {
        //static 不计入 类/对象大小
        Myacls myobj;
        cout <<"Obje size: "<< sizeof(myobj) << endl;
        cout << "Class size: " << sizeof(Myacls) << endl;
    
        // 普通成员变量的存储顺序 是按照在类种的定义顺序。
        // 比较出现的成员变量在内存中有更高的地址
        // 类定义中 public private protected 的数量 不影响类对象的sizeof
        myobj.m_i = 2;
        myobj.m_j = 5;
        myobj.m_k = 8;
    
        printf("myobj.mi = %p\n", &myobj.m_i);
        printf("myobj.mj = %p\n", &myobj.m_j);
        printf("myobj.mk = %p\n", &myobj.m_k);
    
        Myacls *pmyobj = new Myacls();
        printf("pmyobj->mi = %p\n", &pmyobj->m_i);
        printf("pmyobj->mj = %p\n", &pmyobj->m_j);
        printf("pmyobj->mk = %p\n", &pmyobj->m_k);
    
        
    
        return 0;
    }
    
    1. 静态成员在类内 还没赋值的情况下 是声明,只有在赋值的情况下才是定义。
    2. 普通成员的存储顺序就是类内的声明顺序。与protected public private 属性无关。
    3. 结论就是 static 变量的地址等是在编译的时候就注定了。且不属于对象,而是属于类。只有一份儿。

    类成员的字节对齐

    按照1个字节对齐,类结束后,记得恢复默认的字节对齐

    #pragma pack(1) // 定义这个类时,按照1字节对齐
    //统一字节对齐(不对齐)
    class Myacls
    {
    public:
        int m_i;
        static int m_si;
        int m_j;
        static int m_sj;//声明
    
        char m_c;
        int m_k;
        static int m_sk;
    private:
        int m_pria;
        int m_prib;
    public:
        void printMemPoint()
        {
            printf("%d\n", &Myacls::m_i);// 类内成员变量的偏移
            printf("%d\n", &Myacls::m_j);
            printf("%d\n", &Myacls::m_c);
            printf("%d\n", &Myacls::m_k);
            printf("%d\n", &Myacls::m_pria);
            printf("%d\n", &Myacls::m_prib);
    
        }
    
    };
    #pragma pack()// 取消字节对齐,恢复默认对齐
    

    还应该注意,&Myacls::m_i 是针对类内成员变量的偏移,不能用对象去取地址。

    成员变量的读取

    静态成员

    class Myacls
    {
    public:
        int m_i;
        static int m_si;
        int m_j;
    };
    int Myacls::m_si = 10;
    
    int main() {
        Myacls myobj;
        Myacls *pmyobj = new Myacls();
    
        cout << Myacls::m_si << endl;
        cout << myobj.m_si << endl;
        cout << pmyobj->m_si << endl;
    }
    

    对于静态成员变量的读取,三者效果相同

    普通成员

    对于普通成员的访问,编译器是把类对象的首地址加上成员变量的偏移地址

    携带父类的成员变量偏移

    class Father
    {
    public:
        int m_fai;
        int m_faj;
    };
    class Myacls:public Father
    {
    public:
        int m_i;
        static int m_si;
        int m_j;
    
        void myfunc()
        {
            m_i = 5;
            m_j = 6;
        }
    };
    #pragma pack()// 取消字节对齐,恢复默认对齐
    
    int Myacls::m_si = 10;
    
    int main(int argc, char **argv)
    {
        Myacls * pmyobj = new Myacls();
        pmyobj->myfunc();
    
        printf("%d\n", &Myacls::m_i);// 地址是8,已经偏移了
        
        return 0;
    }
    

    需要加上 父类的成员变量偏移。也说的过去,毕竟需要构造父类的东东。

    单一继承下的类内成员布局

    class Father
    {
    public:
        int m_fai;
        int m_faj;
    };
    
    class Myclass :public Father
    {
    public:
        int m_i;
        int m_j;
    };
    
    int main(int argc, char **argv)
    {
        printf("Father%d\n", &Father::m_fai);
        printf("Father%d\n", &Father::m_faj);
    
        printf("Myclass%d\n", &Myclass::m_fai);
        printf("Myclass%d\n", &Myclass::m_faj);
    
        printf("Myclass%d\n", &Myclass::m_i);
        printf("Myclass%d\n", &Myclass::m_j);
    
        //一个子类对象,包含父类的部分
        //从偏移值先看,父类成员先出现,然后是子类成员出现。且顺序与声明顺序相同
    
        return 0;
    }
    
    图片.png

    结论见注释

    单一对象结构 vs 继承对象结构

    class Base 
    {
    public:
        int a;
        char b;
        char c;
        char d;
    };
    
    
    int main(int argc, char **argv)
    {
        //对齐图 内存对齐紧凑
        printf("Base size:%d\n", sizeof(Base));
        printf("Base a:%d\n", &Base::a);
        printf("Base b:%d\n", &Base::b);
        printf("Base c:%d\n", &Base::c);
        printf("Base d:%d\n", &Base::d);
        return 0;
    }
    

    结构紧凑:


    图片.png
    //引入继承关系举例
    class Base 
    {
    public:
        int a;
        char b;
    };
    
    class Base1 : public Base
    {
    public:
        char c;
    };
    
    class Base2 : public Base1
    {
    public:
        char d;
    };
    
    int main(int argc, char **argv)
    {
        //对齐图 内存对齐紧凑
        printf("Base size:%d\n", sizeof(Base));
        printf("Base1 size:%d\n", sizeof(Base1));
        printf("Base2 size:%d\n", sizeof(Base2));
    
        //1. windows 和 linux 本身布局不同
            //编译器本身的优化不同与实现布局不同
    
        Base1  mybase1;
        Base2  mybase2;
        //linux 不能用 memcpy 把base1的内容往base2拷贝,会把base2 的对象成员 的值修改掉。
        return 0;
    

    结构打散:
    windows 和 linux 这种继承关系的成员对象布局不同,但都会根据对齐方式,将结构打散
    windes:


    windows.png

    Linux:


    图片.png

    所以不要执行以下代码:

    Base1 myBase1;
    Base2 myBase2;
    memcpy(myBase2, myBase1, sizeof(myBase1)
    

    会破坏对象内部的结构

    单类单继承带有虚函数的数据成员布局

    单类带有虚函数

    class Myclass
    {
    public:
        int m_i;
        int m_j;
        virtual void myvirfunc()
        {}
        Myclass()
        {
            int abc = 1;
        }
        ~Myclass()
        {
            int def = 0;
        }
    };
    
    int main(int argc, char **argv)
    {
        //单个类引入虚函数,则会增加如下:
        //1. 编译时,编译器产生虚函数表
        //2. 对象中,会多出vptr,虚函数表指针
        //3. 扩展构造函数,给vptr赋值
        //4. 多重继承,每个父类中都有虚函数的话,子类对象会有多个vptr
            //子类与第一个继承的父类共用一个vptr
        //5. 析构函数也扩展了vptr的相关代码。
    
        cout << sizeof(Myclass) << endl;
        printf("myclass:m_i = %d\n", &Myclass::m_i);//4
        printf("myclass:m_j = %d\n", &Myclass::m_j);//8
        
        //可以看图
        return 0;
    }
    
    反汇编下的赋值.png 单类带有虚函数.png

    继承父类带有虚函数

    class Base
    {
    public:
        int m_bi;
        virtual void mybvirfunc()
        {}
    };
    class Myclass: public Base
    {
    public:
        int m_i;
        int m_j;
        virtual void myvirfunc()
        {}
        Myclass()
        {
            int abc = 1;
        }
        ~Myclass()
        {
            int def = 0;
        }
    };
    
    int main(int argc, char **argv)
    {
        //看图
        cout << sizeof(Myclass) << endl;
        printf("myclass:m_bi = %d\n", &Myclass::m_i);//4
        printf("myclass:m_i = %d\n", &Myclass::m_i);//8
        printf("myclass:m_j = %d\n", &Myclass::m_j);//12
        
        return 0;
    
    }
    
    父类带有虚函数.png

    继承中父类不带虚函数,子类带虚函数

    //不带虚函数
    class Base
    {
    public:
        int m_bi;
    };
    class Myclass: public Base
    {
    public:
        int m_i;
        int m_j;
        virtual void myvirfunc()
        {}
        Myclass()
        {
            int abc = 1;
        }
        ~Myclass()
        {
            int def = 0;
        }
    };
    
    int main(int argc, char **argv)
    {
        //看图
        printf("myclass:m_bi = %d\n", &Myclass::m_bi);//0
        printf("myclass:m_i = %d\n", &Myclass::m_i);//8
        printf("myclass:m_j = %d\n", &Myclass::m_j);//12
    
        Myclass myobj;
        myobj.m_bi = 0;
        myobj.m_i = 1;
        myobj.m_i = 2;
        
        
        return 0;
    
    }
    
    实际.png

    虽然从成员偏移看是左边,但是从调试结果看,右边的正确。

    对象结构.png

    钉在最前的依旧是虚函数表指针。是子类的虚函数表指针。

    相关文章

      网友评论

          本文标题:类内成员变量的读取与布局

          本文链接:https://www.haomeiwen.com/subject/skpwiltx.html