类内成员变量的读取与布局
2021-08-29 本文已影响0人
404Not_Found
- 作者: 雪山肥鱼
- 时间: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;
}
- 静态成员在类内 还没赋值的情况下 是声明,只有在赋值的情况下才是定义。
- 普通成员的存储顺序就是类内的声明顺序。与protected public private 属性无关。
- 结论就是 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钉在最前的依旧是虚函数表指针。是子类的虚函数表指针。