c++对象内存布局学习笔记(二)

前言

不同平台下的字节宽度,以下的示例均在x64的平台上测试,编译器vs2015。

数据类型说明32位字节数64位字节数取值范围
bool布尔型11true,false
char字符型11-128~127
unsigned char无符号字符型110~255
short短整型22-32768~32767
unsigned short无符号短整型220~65535
int整型44-2147483648~2147483647
unsigned int无符号整型440~4294967295
long长整型48
unsigned long无符号长整型48
long long长整型88-264~264-1
float单精度浮点数44范围-2128~2128 精度为6~7位有效数字
double双精度浮点数88范围-21024~21024 精度为15~16位
long double扩展精度浮点数88范围-21024~21024 精度为15~16位
*地址48

一、单个的类

1、不包含虚函数的类

1.1 代码示例

注意类CBase0中的析构函数,不是虚函数。类中不存在任何的虚函数。

#include <iostream>
#include <string>
using namespace std;
class CBase0 {
public:
	CBase0() :m_n1(0), m_l1(1) {}
	~CBase0() {}
	void print() { cout << "CBase0::print" << endl; }
public:
	short m_n1;
	long long m_l1;
};
int main()
{
	CBase0*pBase = new CBase0;
	return 0;
}
1.2 调试结果

在这里插入图片描述
gdb调试下的结果,因为没有虚汗数,所以不包含虚表:
在这里插入图片描述

1.3 小结

没有虚函数的类,不存在虚表。内存分布只有成员变量,成员变量根据声明的先后顺序排列。占用的字节空间根据平台的不同不一样,且会内存对齐到最大的占用字节数。上图黄色表示short m_n1绿色表示long longm_l1

2、包含虚函数

类中包含虚函数(虚析构函数或者普通的虚函数)

2.1 在1.1源码的基础上给析构函数增加了virtual关键字,声明为虚函数
2.2 调试结果的截图

在这里插入图片描述

2.3 小结

包含有虚函数的类,存在虚表,2.2中监视1中_vfptr的内容。虚表地址0x00007ff77955ebe0,是对象的内存中前8个字节(2.2内存1第一组框表示),后续的2个框为类的成员变量的空间,同样的是内存对齐。
当包含有多个虚函数的时候,在虚表中多个虚函数的地址根据声明的顺序依次排列。将2.2中类的成员函数print加上了virtual关键字修饰,如下图:
在这里插入图片描述

二、单层继承的类

1、无多态重载的虚函数

1.1 代码示例——基类CBase和派生类CDriver,派生类中没有除析构函数外的虚函数
#include <iostream>
#include <string>
using namespace std;
class CBase {
public:
	CBase() :m_n1(1) {}
	virtual ~CBase() {}
	virtual void print() { cout << "CBase::print" << endl; }
public:
	short m_n1;
};
class CDriver :public CBase {
public:
	CDriver() :m_n2(2) {}
	virtual ~CDriver() {}
private:
	int m_n2;
};
int main()
{
	CDriver* pDriver = new CDriver;
	CBase*pBase = pDriver;
	return 0;
}
1.1.1 调试的结构
1.1.1.1 派生类实例化对象的内存分布

在这里插入图片描述
上图中pDriver为派生类的对象,和一大节2.1小结一样,包含有同样的结构:前8个字节为虚表(虚表中有两个虚函数的地址,虚析构函数和继承自基类的虚函数);虚表后面紧随的是基类的成员函数,同样是根据声明顺序一次排列的。在虚表描述结束后是派生类CDriver的成员函数空间(注意:当前派生类是没有虚函数的)。

1.1.1.2 基类指针

将实例化的对象指针强制转到基类指针上就是pBase指针,他的结构如下描述:
在这里插入图片描述
红色部分是派生类CDriver的实例化对象的内存分布,和上面1.2.1描述一致。蓝色部分是基类CBase自己的内存分布,因为这里的CBase并不是一个纯接口类(虚函数为纯虚函数的基类),所以蓝色部分__vfptrCBase的虚表。可以通过观察成员变量很好的区分基类和派生类的关系。
因为是将派生类的指针指向了基类,右边的内存分布和1.2.1描述一致。

1.2 代码示例——基类CBase和派生类CDriver,派生类中有除析构函数外的虚函数
#include <iostream>
#include <string>
using namespace std;
class CBase {
public:
	CBase() :m_n1(1) {}
	virtual ~CBase() {}
	virtual void print() { cout << "CBase::print" << endl; }
public:
	short m_n1;
};
class CDriver :public CBase {
public:
	CDriver() :m_n2(2) {}
	virtual ~CDriver() {}
	virtual void driver_print() { cout << "CDriver::driver_print" << endl; }
private:
	int m_n2;
};
int main()
{
	CDriver* pDriver = new CDriver;
	CBase*pBase = pDriver;
	return 0;
}
1.2.1 调试的结构
1.2.1.1 派生类对象内存结构分布

在这里插入图片描述
从上图看,使用vs2015编译器 蓝色部分未显示出CDriver的虚函数driver_print,【留疑问,使用gdb验证】。按理说是根据声明顺序依次排列的。

1.2.1.2 基类对象的内存分布结构

在这里插入图片描述
注意图中第6行 00.exe!CBase::print(void),这时候是基类的函数。

2、有多态重载的虚函数

2.1 代码示例——派生类中有多态结构
#include <iostream>
#include <string>
using namespace std;
class CBase {
public:
	CBase() :m_n1(1) {}
	virtual ~CBase() {}
	virtual void print() { cout << "CBase::print" << endl; }
public:
	short m_n1;
};
class CDriver :public CBase {
public:
	CDriver() :m_n2(2) {}
	virtual ~CDriver() {}
	virtual void print() { cout << "CDriver::print" << endl; }
private:
	int m_n2;
};
int main()
{
	CDriver* pDriver = new CDriver;
	CBase*pBase = pDriver;
	return 0;
}

上面的代码和1.1中的代码差别就是CDriver中使用多态重载了基类的虚函数print,派生类中打印了子类的字符串。

2.2 调试的结果
2.2.1 派生类的内存对象

在这里插入图片描述
红的部分已经替换成了派生类的函数,这其实就是多态的真正实现。

三、多层继承

1、示例代码

#include <iostream>
#include <string>
using namespace std;
class CBase {
public:
	CBase() :m_n1(1) {}
	virtual ~CBase() {}
	virtual void print() { cout << "CBase::print" << endl; }
public:
	short m_n1;
};
class CDriver :public CBase {
public:
	CDriver() :m_n2(2) {}
	virtual ~CDriver() {}
	//virtual void print() { cout << "CDriver::print" << endl; }
private:
	int m_n2;
};
class CDriver1 :public CDriver {
public:
	CDriver1() :m_n2(2) {}
	virtual ~CDriver1() {}
	//virtual void print() { cout << "CDriver1::print" << endl; }
private:
	int m_n2;
};
int main()
{
	CDriver1* pDriver = new CDriver1;
	CBase*pBase = pDriver;
	return 0;
}

2、不同层级派生类重载虚函数时

2.1 派生类1和派生类2不重载基类虚函数

代码同1描述,调试的结果:
在这里插入图片描述

2.2 派生类1重载基类的虚函数

代码1描述中,解开屏蔽的第16行代码,调试的结果:
在这里插入图片描述
2.1中的CBase::print()变成了CDriver::print(),已经发生了替换,运行时体现多态。

2.3 派生类2中重载基类的虚函数

代码1描述中,解开屏蔽的第24行,调试的结果:
在这里插入图片描述
红色部分已经变成了CDriver1::print(),同样已经发生了替换,运行时体现多态。

3、多基类继承的派生类

派生类CDriver继承两个基类,实例化的对象指针指向两个不同的基类指针。

3.1 示例代码
#include <iostream>
#include <string>
using namespace std;
class CBase {
public:
	CBase() :m_nBase(1) {}
	virtual ~CBase() {}
	virtual void print() { cout << "CBase::print" << endl; }
public:
	short m_nBase;
};
class CBase1 {
public:
	CBase1() :m_nBase1(2) {}
	virtual ~CBase1() {}
	virtual void print1() { cout << "CBase1::print" << endl; }
public:
	short m_nBase1;
};

class CDriver :public CBase, public  CBase1 {
public:
	CDriver() :m_nDriver(3) {}
	virtual ~CDriver() {}
	//virtual void print() { cout << "CDriver::print" << endl; }
private:
	int m_nDriver;
};

int main()
{
	CBase*pBase = new CDriver;
	CBase1*pBase1 = new CDriver;
	return 0;
}
3.2 调试结果
3.2.1 当派生类中不重载基类的虚函数时,基类指针CBase所指向的对象内存分布:

在这里插入图片描述
红色部分是基类CBase的虚表(虚函数指针)和成员变量的内存分布,绿色部分是实例对象CDriver类的分布,两个基类的虚表按继承的顺序分布,展开如下:
在这里插入图片描述
注意看基类的虚函数CBase::printCBase1::print,并未重载,都是基类的实现,另一个基类的对象内存分布如下:
在这里插入图片描述

3.2.2 派生类重载基类的虚函数时

去掉源码中的第25行屏蔽,重载CBase的虚函数实现,不对基类CBase1重载,如下:
在这里插入图片描述
在这里插入图片描述
从上面不难发现,派生类重载了哪一个基类的虚函数,则派生类的虚表中的哪一个基类的虚函数会被替换成派生类的,这就是多态的实现了。

四、菱形继承的内存分布

五、总结

1 vs编译器和gcc/g++编译器的虚表是不一致的

1.1 vs编译器无虚函数(包括虚的析构函数)时,存在虚表。但是gcc编译器是不存在虚表的,但是当类包含有一个除了析构函数外的虚函数时是有虚表的
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值