深度探索C++对象模型(13)——数据语义学(3)——单类继承下数据成员布局

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的值

 

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值