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

2021-08-29  本文已影响0人  404Not_Found

静态成员属于类

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

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

上一篇下一篇

猜你喜欢

热点阅读