C++对象的内存布局

8 篇文章 0 订阅

C++对象的内存布局

        内存布局是属于较深层次的知识,很多问题往深了讲都是不清楚内存布局的原理。最近读到一本书,里面讲了一部分C++对象的内存布局,让我对很多以前的问题都豁然开朗了。书上篇幅较大,我加上自己的理解总结了下。

        分为三部分:简单对象,单继承,多继承

一:简单对象

  1. 非静态成员变量和虚函数是决定类大小的唯一两个因素
  2. 非静态成员变量在类的内存里会有字节对齐
  3. 如果对象中包含虚函数,会增加4个字节的空间,不论有多少个虚函数。
  4. 静态成员变量,静态成员函数和非静态成员函数不会影响对象内存的大小。静态成员变量存储在内存的静态数据区。另外:非静态成员函数和程序普通函数的函数区别是,类的非静态成员函数函数有this指针,所以可以访问类的成员。静态成员函数不可以调用类的非静态成员,因为静态成员函数不含this指针。

下面用实验说明:

class simpleClass
{
public:
	static int nCount;
	double nValue;
	char c,d;

	simpleClass(){};
	virtual ~simpleClass(){};

	int getValue(void);
	virtual void foo(void){};
	static void addCount(){};
};

int main()
{
    simpleClass aSimple;

    printf("Object start address: %x\n", &aSimple);
    printf("nValue address: %x\n", &aSimple.nValue);
    printf("c address: %x\n", &aSimple.c);printf("c address: %x\n", &aSimple.d);
    printf("size: %d\n", sizeof(simpleClass));getchar();return 0;
}
 

 输出: 

Object start address:1efdec

nValue address: 1efdf4

c address:1efdfc

d address: 1efdfd

size: 24

因为double是8个字节,所以虚函数表和两个char的的成员都按照8字节对齐,所以总大小是24。

8字节vptr 虚函数指针
8字节double nValue
8字节char c,d

二:单继承

先举例

class simpleClass
{
public:
	static int nCount;
	int nValue;
	char c;

	simpleClass(){};
	virtual ~simpleClass(){};

	int getValue(void);
	virtual void foo(void){};
	static void addCount(){};
};
class derivedClass:public simpleClass
{
public:
	int nSubValue;
	derivedClass(){};
	~derivedClass(){};
	virtual void foo(void){};
};
int main()
{
	derivedClass aSimple;
	printf("Object start address: %x\n", &aSimple);
	printf("nValue address: %x\n", &aSimple.nValue);
	printf("c address: %x\n", &aSimple.c);
	printf("nSubValue address: %x\n", &aSimple.nSubValue);
	printf("size: %d\n", sizeof(derivedClass));

	getchar();
	return 0;
}

输出:

Object start address:23fa5c

nValue address: 23fa60

c address:23fa64

nSubValue address: 23fa68

size: 16

可见:

在构造一个派生类的实例时首先够着一个基类的实例,而这个基类的实例在派生类的实例销毁之后销毁。derivedClass的大小是16字节,基类simpleClass的大小是12字节。派生类增加了一个整型变量nSubValue,在内存布局中放在基类的后面。而且派生类在构造时不会再创建一个新的虚函数表,而是在基类的虚函数表中增加或者修改。

三:多继承-简单

  1. 与但继承相同,创建派生类的对象时,要首先创建基类的对象。由于多继承中一个派生类有多个基类,因此创建基类的对象要遵循派生类声明的顺序。
  2. simpleClass1 的一个对象的大小是12字节,simpleClass2的一个对象的大小是8字节。而派生类增加了4个字节的整型成员数据,大小是24字节。
  3. 在多继承中药注意避免二义性。如上面的例子两个基类都定义了getValue()函数。如果一个派生类的实例调用getValue函数会报二义性错误。
  4. 若simpleClass1和simpleClass2的内存对齐不一样,那么派生类继承他们以后,会重新统一内存对齐。比如若simpleClass1是按4字节对齐,simpleClass2是按8字节对齐。当derivedClass继承他们以后,simpleClass1也会按照8字节对齐。
下面看例子:

class simpleClass1
{
public:
	int nValue1;
	char c;

	simpleClass1(){};
	virtual ~simpleClass1(){};

	int getValue(void);
	virtual void foo1(void){};
};
class simpleClass2
{
public:
	int nValue2;

	simpleClass2(){};
	virtual ~simpleClass2(){};

	int getValue(void);
	virtual void foo2(void){};
};
class derivedClass:public simpleClass1,public simpleClass2
{
public:
	int nSubValue;
	derivedClass(){};
	~derivedClass(){};
	virtual void foo2(void){};
};
int main()
{
	derivedClass aSimple;
	printf("Object start address: %x\n", &aSimple);
	printf("nValue1 address: %x\n", &aSimple.nValue1);
	printf("c address: %x\n", &aSimple.c);
	printf("nValue2 address: %x\n", &aSimple.nValue2);
	printf("nSubValue address: %x\n", &aSimple.nSubValue);
	printf("size: %d\n", sizeof(aSimple));

	getchar();
	return 0;
}

输出:

Object start address:1bfe04

nValue1 address: 1bfe08

c address:1bfe0c

nValue2 address: 1bfe14

nSubValue address: 1bfe18

size: 24


四:多继承-菱形继承(使用虚继承)

考虑如下的菱形继承

如果不适用虚拟继承,内存布局将会如上面右图所示:会有两个baseClass实例。

如果使用虚拟继承,则内存布局如下


使用虚拟继承以后

  1. baseClass只创建了一个实例,其数据成员的地址相同
  2. baseClass的实例放在derivedClass实例的内存空间中的最后
  3. 为了使用虚拟继承,每个使用虚拟继承的类都会添加一个虚基类表的指针(virtual base table)来实现,因此照成空间变大。


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

zlingh

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值