1.单一继承下的数据成员布局
代码1:
#include <iostream>
#include <stdio.h>
using namespace std;
class FAC //父类
{
public:
int m_fai;
int m_faj;
};
class MYACLS :public FAC //子类
{
public:
int m_i;
int m_j;
};
int main()
{
printf("FAC::m_fai = %d\n", &FAC::m_fai);
printf("FAC::m_faj = %d\n", &FAC::m_faj);
printf("MYACLS::m_fai = %d\n", &MYACLS::m_fai);
printf("MYACLS::m_faj = %d\n", &MYACLS::m_faj);
printf("MYACLS::m_i = %d\n", &MYACLS::m_i);
printf("MYACLS::m_j = %d\n", &MYACLS::m_j);
}
运行结果:
、
内存布局图:
父类FAC
子类MYACLS
结论:
(1)一个子类对象,所包含的内容,是他自己的成员,加上他父类的成员的总和;
(2)从偏移值看,父类成员先出现,然后才是孩子类成员。
代码2:
#include <iostream>
#include <stdio.h>
using namespace std;
class Base //sizeof = 8字节;
{
public:
int m_i1;
char m_c1;
char m_c2;
char m_c3;
};
int main()
{
cout << sizeof(Base) << endl; //8字节,数据布局(内存排列上)紧凑;
printf("Base::m_i1 = %d\n", &Base::m_i1);
printf("Base::m_c1 = %d\n", &Base::m_c1);
printf("Base::m_c2 = %d\n", &Base::m_c2);
printf("Base::m_c3 = %d\n", &Base::m_c3);
}
运行结果:
内存布局图:
类Base
代码3:
#include <iostream>
#include <stdio.h>
using namespace std;
class Base1
{
public:
int m_i1;
char m_c1;
};
class Base2 :public Base1
{
public:
char m_c2;
};
class Base3 :public Base2
{
public:
char m_c3;
};
int main()
{
cout << sizeof(Base1) << endl; //8
cout << sizeof(Base2) << endl; //12
cout << sizeof(Base3) << endl; //16
printf("Base3::m_mi1 = %d\n", &Base3::m_i1);
printf("Base3::m_mc1 = %d\n", &Base3::m_c1);
printf("Base3::m_mc2 = %d\n", &Base3::m_c2);
printf("Base3::m_mc3 = %d\n", &Base3::m_c3);
}
运行结果:、
VS2017下:
Linux下g++5.4.0下运行结果:
内存布局图:
VS2017下:
类Base1:
类Base2:
类Base3:
在VS下Base3包含Base2子对象
Linux下g++5.4.0下:
类Base1:
类Base2:
类Base3:
在Linux的g++下Base3不包含Base2子对象
结论:
引入继承关系后,可能会带来内存空间的额外增加,因为产生了层次结构浪费了一些用于字节对齐的内存空间
linux上windows上数据布局不一样,说明:
a)编译器在不断的进步和优化;
b)不同厂商编译器,实现细节也不一样;
c)内存拷贝就要谨慎;
比如:
Base2 mybase2obj;
Base3 mybase3obj;
//不能用memcpy内存拷贝把Base2内容直接Base3里拷贝,因为这样的拷贝会覆盖m_c3
2.单类单继承虚函数下数据成员布局
(1)单个类带虚函数的数据成员布局
类中引入虚函数时,会有额外的成本付出
<1>编译的时候,编译器会产生虚函数表
<2>对象中会产生 虚函数表指针vptr,用以指向虚函数表的首地址
<3>增加或者扩展构造函数,增加给虚函数表指针vptr赋值的代码,让vptr指向虚函数表;
<4>如果多重继承,比如你继承了2个父类,每个父类都有虚函数的话,每个父类都会有vptr,那继承时,子类就会把这两根vptr都继承过来,如果子类还有自己额外的虚函数的话,子类与第一个基类共用一个vptr
<5>析构函数中也被扩展增加了虚函数表指针vptr相关的赋值代码,感觉这个赋值代码似乎和构造函数中代码相同;
代码:
#include <iostream>
#include <stdio.h>
using namespace std;
class MYACLS
{
public:
int m_i;
int m_j;
virtual void myvirfunc() {} //虚函数
MYACLS()
{
int abc = 1; //方便加断点
}
~MYACLS()
{
int def = 0;//方便加断点
}
};
int main()
{
MYACLS a;
cout << sizeof(MYACLS) << endl;
printf("MYACLS::m_i = %d\n", &MYACLS::m_i);
printf("MYACLS::m_j = %d\n", &MYACLS::m_j);
MYACLS myobj;
myobj.m_i = 3;
myobj.m_j = 6;
}
运行结果:
反汇编:
构造函数:
析构函数:
内存详情查看:
内存布局:
(2)单一继承父类带虚函数的数据成员布局
代码:
#include <iostream>
#include <stdio.h>
using namespace std;
class Base1
{
public:
int m_bi;
virtual void mybvirfunc() {}
Base1()
{
printf("Base1::Base1()的this指针是:%p!\n", this);
}
};
class MYACLS :public Base1
{
public:
int m_i;
int m_j;
virtual void myvirfunc() {} //虚函数
MYACLS()
{
int abc = 1; //方便加断点
printf("MYACLS::MYACLS()的this指针是:%p!\n", this);
}
~MYACLS()
{
int def = 0;//方便加断点
}
};
int main()
{
cout << sizeof(MYACLS) << endl;
printf("MYACLS::m_bi = %d\n", &MYACLS::m_bi);//因为m_bi在父类Base1中,所以这里取到的偏移量是相对于父类Base1的偏移量
printf("MYACLS::m_i = %d\n", &MYACLS::m_i);
printf("MYACLS::m_j = %d\n", &MYACLS::m_j);
MYACLS myobj;
myobj.m_i = 3;
myobj.m_j = 6;
myobj.m_bi = 9;
}
运行结果:
内存详情查看:
内存布局:
可以从上述代码运行结果的两个this指针的地址也可以看出这里Base1中类子对象的起始地址相对于MYACLS类对象的偏移为0,而这里的起始也就是Base1中vptr的值,因为继承的第一个类与子类共享vptr,所以父类子对象的起始地址与子类子对象的起始地址相同,都是vptr
(3)单一继承父类不带虚函数的数据成员布局
代码:
#include <iostream>
#include <stdio.h>
using namespace std;
class Base1
{
public:
int m_bi;
Base1()
{
printf("Base1::Base1()的this指针是:%p!\n", this);
}
};
class MYACLS :public Base1
{
public:
int m_i;
int m_j;
virtual void myvirfunc() {} //虚函数
MYACLS()
{
int abc = 1; //方便加断点
printf("MYACLS::MYACLS()的this指针是:%p!\n", this);
}
~MYACLS()
{
int def = 0;//方便加断点
}
};
int main()
{
MYACLS a;
cout << sizeof(MYACLS) << endl;
printf("MYACLS::m_bi = %d\n", &MYACLS::m_bi);//因为m_bi在父类Base1中,所以这里取到的偏移量是相对于父类Base1的偏移量
//但是此处我们并不知道它相对于子类MYACLS的偏移量
printf("MYACLS::m_i = %d\n", &MYACLS::m_i);
printf("MYACLS::m_j = %d\n", &MYACLS::m_j);
MYACLS myobj;
myobj.m_i = 3;
myobj.m_j = 6;
myobj.m_bi = 9;
}
运行结果:
内存详情查看:
可以发现对m_bi的赋值在偏移量为4处,即m_bi在子类的中的偏移量为4,子类MYACLS的vptr还是在子类对象地址的开始
内存布局图:
可以从上述代码运行结果的两个this指针的地址也可以看出这里Base1中类子对象的起始地址相对于MYACLS类对象的偏移为4,而这里的起始也就是m_bi的值