C++类内存分布

本文探讨了C++中空类、含有数据成员、普通方法、虚函数的类以及继承情况下的对象大小。结果显示,空类大小为1,数据成员会按实际大小占用空间,虚函数会增加4字节用于存放虚函数表指针,继承不会增加对象大小,但会影响内存布局。同时,多继承时,虚函数表会包含所有父类的虚函数。
摘要由CSDN通过智能技术生成

无继承

空类

class A
{
    
};

void test1()
{
	A a;
	std::cout << "class A size:" << sizeof(a) << "\n";
	A array_a[10];
	std::cout << "class array a size:" << sizeof(array_a) << "\n";
}

从上图的输出结果可以看出,一个空类的大小并非想象中的0,而是1。

而且从上图中也可以看出,在数组情况下,其值为未初始化的默认栈值。

仅存在数据元素的类

class B
{
	uint8_t var1;
};

class C
{
	uint16_t var1;
};

class D
{
	uint8_t var1;
	uint32_t var2;
};

void test2()
{
	B b;
	std::cout << "class B size:" << sizeof(b) << "\n";
	C c;
	std::cout << "class C size:" << sizeof(c) << "\n";
	D d;
	std::cout << "class D size:" << sizeof(d) << "\n";
}

从上图中可知,

当仅定义一个 uint8_t 类型时,所占用的空间和空类是相同的。

当定义的数据类型为 uint16_t 类型时,占用的空间与数据元素保持相同。

当同时定义多个数据类型时,则由于数据对齐的原因,导致数据大小大于了实际的数据类型的相加的总大小。

仅有普通方法的类

class E
{
public:
	void fun1() 
	{

	}
	int fun2() 
	{
		return 0;
	}
};

void test3()
{
	E e;
	std::cout << "class E size:" << sizeof(e) << "\n";
	printf("e position:%08X\n", &e);
	printf("E::fun1 position:%08X\n", &E::fun1);
	printf("E::fun2 position:%08X\n", &E::fun2);
}

从上述截图中可知,仅有普通方法的类,其大小并没有变化,与空类相同。

有虚函数的类

class F
{
public:
	void fun1()
	{

	}
	virtual int fun2()
	{
		return 0;
	}
};

void test4()
{
	F f;
	std::cout << "class F size:" << sizeof(f) << "\n";
	printf("f position:%08X\n", &f);
	printf("F::fun1 position:%08X\n", &F::fun1);
	printf("F::fun2 position:%08X\n", &F::fun2);

	std::cout << "Hello World!\n";
}

从上图中输出的信息中可以看出,当函数具备虚函数时,则所占空间大小变为了4。同时从后面图中,可以得知,所占用的4个字节所存储的内容为vfptr,即虚函数表指针。通过比较前后函数的地址,可知虚函数表中的内容即为虚函数的地址。

之后我们继续添加虚函数的个数。

class F
{
public:
	void fun1()
	{

	}
	virtual int fun2()
	{
		return 0;
	}
	virtual int fun3()
	{
		return 0;
	}
};

void test4()
{
	F f;
	std::cout << "class F size:" << sizeof(f) << "\n";
	printf("f position:%08X\n", &f);
	printf("F::fun1 position:%08X\n", &F::fun1);
	printf("F::fun2 position:%08X\n", &F::fun2);

	std::cout << "Hello World!\n";
}

通过继续添加虚函数的个数,可知,多个虚函数并不会影响类对象所占空间的大小。但却会影响虚函数表的内容。如第二个图中所示,虚函数表中的内容变为了两个函数指针。

多情况混合

class G
{
public:
	uint32_t var1;
	void fun1()
	{

	}
	virtual int fun2()
	{
		return 0;
	}
	virtual int fun3()
	{
		return 0;
	}
	uint32_t var2;
};

void test5()
{
	G g;
	std::cout << "class G size:" << sizeof(g) << "\n";
	printf("g position:%08X\n", &g);
	printf("G::fun1 position:%08X\n", &G::fun1);
	printf("G::fun2 position:%08X\n", &G::fun2);

	std::cout << "Hello World!\n";
}

由上图可知,在几种元素都存在的情况下,数据元素会按照实际情况占用空间大小。虚函数存在时则会有一个固定4字节的空间用来存放虚函数表指针。

单继承

父类与子类仅有数据元素

class H
{
public:
	uint32_t var1;
	uint32_t var2;
};

class I : public H
{
public :
	uint32_t var3;
	uint8_t var4;
};


void test6()
{
	I i;
	std::cout << "class I size:" << sizeof(i) << "\n";

	std::cout << "Hello World!\n";
}

如上图所示可知,在这种情况下,父类数据元素会与子类数据元素进行顺序叠加,其中父类数据元素在子类数据元素之前。

父类与子类仅有普通方法

class J
{
public:
	void fun1() {
	
	}

	int fun2() {
		return 0;
	}
};

class K : public J
{
public:
	void fun3() {

	}
	int fun4() {
		return 0;
	}
};

void test7()
{
	K k;
	std::cout << "class K size:" << sizeof(k) << "\n";

	printf("k position:%p\n", &k);
	printf("J::fun1 position:%p\n", &J::fun1);
	printf("J::fun2 position:%p\n", &J::fun2);
	printf("K::fun3 position:%p\n", &K::fun3);
	printf("K::fun4 position:%p\n", &K::fun4);

	std::cout << "Hello World!\n";
}

可知,单纯的普通方法并不会影响到类对象的大小。无论是否继承。

父类无虚函数,子类有虚函数

与有虚函数的类相同结论。

父类有虚函数,子类无虚函数

class L
{
public:
	void fun1() {

	}

	virtual int fun2() {
		return 0;
	}
};

class M : public L
{
public:
	void fun3() {

	}
	int fun4() {
		return 0;
	}
};


void test8()
{
	M m;
	std::cout << "class M size:" << sizeof(m) << "\n";

	printf("m position:%p\n", &m);
	printf("L::fun1 position:%p\n", &L::fun1);
	printf("L::fun2 position:%p\n", &L::fun2);
	printf("M::fun3 position:%p\n", &M::fun3);
	printf("M::fun4 position:%p\n", &M::fun4);

	std::cout << "Hello World!\n";
}

如上图所示,在继承了虚函数后,则也会占据4个字节,用来存放虚函数表指针。其中虚函数表中存放了父类的虚函数表。

父类与子类均有虚函数

class N
{
public:
	void fun1() {

	}

	virtual int fun2() {
		return 0;
	}
};

class O : public N
{
public:
	void fun3() {

	}
	virtual int fun4() {
		return 0;
	}
};

void test9()
{
	O o;
	std::cout << "class O size:" << sizeof(o) << "\n";

	printf("o position:%p\n", &o);
	printf("N::fun1 position:%p\n", &N::fun1);
	printf("N::fun2 position:%p\n", &N::fun2);
	printf("O::fun3 position:%p\n", &O::fun3);
	printf("O::fun4 position:%p\n", &O::fun4);

	std::cout << "Hello World!\n";
}

从上面可知,父子均有虚函数时,父子类的虚函数表中会顺序存放两者的虚函数,父类的虚函数在前,子类的虚函数在后,同时会用0x00000000来结束虚函数表。

多种情况混合

class P
{
public:
	uint32_t var1;
	void fun1() {

	}

	virtual int fun2() {
		return 0;
	}
};

class Q : public P
{
public:
	uint32_t var2;
	void fun3() {

	}
	virtual int fun4() {
		return 0;
	}
};


void test10()
{
	Q q;
	std::cout << "class q size:" << sizeof(q) << "\n";

	printf("q position:%p\n", &q);
	printf("P::fun1 position:%p\n", &P::fun1);
	printf("P::fun2 position:%p\n", &P::fun2);
	printf("Q::fun3 position:%p\n", &Q::fun3);
	printf("Q::fun4 position:%p\n", &Q::fun4);

	std::cout << "Hello World!\n";
}

没有什么奇特的,同样都是最开始为虚函数表指针,之后为父类的数据元素,最后为子类的数据元素。虚函数表中,父类的虚函数在前,子类的虚函数在后。最后以0x00000000结束。

多继承

多种情况混合

class R
{
public:
	uint32_t var1;
	void fun1() {

	}

	virtual int fun2() {
		printf("class R fun2\n");
		return 0;
	}

	virtual int fun3() {
		return 0;
	}
};

class S
{
public:
	uint32_t var1;
	void fun1() {

	}

	virtual int fun2() {
		printf("class S fun2\n");
		return 0;
	}
};


class T : public R, public S
{
public:
	uint32_t var2;
	int fun3() {
		return 2;
	}
	virtual int fun4() {
		return 0;
	}
};


void test11()
{
	T t;
	std::cout << "class t size:" << sizeof(t) << "\n";

	void* tmpt;
	R* rt;
	S* st;
	rt = &t;
	st = &t;
	tmpt = &t;

	// t.fun2();
	rt->fun2();
	st->fun2();

	printf("R::fun1 position:%p\n", &R::fun1);
	printf("R::fun2 position:%p\n", &R::fun2);
	printf("R::fun3 position:%p\n", &R::fun3);

	printf("S::fun1 position:%p\n", &S::fun1);
	printf("S::fun2 position:%p\n", &S::fun2);

	printf("T::fun3 position:%p\n", &T::fun3);
	printf("T::fun4 position:%p\n", &T::fun4);

	std::cout << "Hello World!\n";
}

如图中可知,当在多继承中,如果父类中丢存在虚函数,则在最终的子类中,会按照顺序依次排布父类相关内容。

首先排布第一个父类的虚函数表指针,之后是第一个父类的数据元素。

然后排布的是第二个父类的虚函数表指针,之后是第二个父类的数据元素。

最后才是子类所定义的数据元素。

而子类中的虚函数则会存放在第一个父类的虚函数表中。结束时依旧以 0x00000000 结尾。

同时上面比较神奇的是,

rt = &t;

st = &t;

但是最终 rt 和 st的值并不相同。此处估计有个类型转换。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值