栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > C/C++/C#

C++类内存分布

C/C++/C# 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

C++类内存分布

无继承

空类
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:%08Xn", &e);
	printf("E::fun1 position:%08Xn", &E::fun1);
	printf("E::fun2 position:%08Xn", &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:%08Xn", &f);
	printf("F::fun1 position:%08Xn", &F::fun1);
	printf("F::fun2 position:%08Xn", &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:%08Xn", &f);
	printf("F::fun1 position:%08Xn", &F::fun1);
	printf("F::fun2 position:%08Xn", &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:%08Xn", &g);
	printf("G::fun1 position:%08Xn", &G::fun1);
	printf("G::fun2 position:%08Xn", &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:%pn", &k);
	printf("J::fun1 position:%pn", &J::fun1);
	printf("J::fun2 position:%pn", &J::fun2);
	printf("K::fun3 position:%pn", &K::fun3);
	printf("K::fun4 position:%pn", &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:%pn", &m);
	printf("L::fun1 position:%pn", &L::fun1);
	printf("L::fun2 position:%pn", &L::fun2);
	printf("M::fun3 position:%pn", &M::fun3);
	printf("M::fun4 position:%pn", &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:%pn", &o);
	printf("N::fun1 position:%pn", &N::fun1);
	printf("N::fun2 position:%pn", &N::fun2);
	printf("O::fun3 position:%pn", &O::fun3);
	printf("O::fun4 position:%pn", &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:%pn", &q);
	printf("P::fun1 position:%pn", &P::fun1);
	printf("P::fun2 position:%pn", &P::fun2);
	printf("Q::fun3 position:%pn", &Q::fun3);
	printf("Q::fun4 position:%pn", &Q::fun4);

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

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

多继承

多种情况混合
class R
{
public:
	uint32_t var1;
	void fun1() {

	}

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

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

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

	}

	virtual int fun2() {
		printf("class S fun2n");
		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:%pn", &R::fun1);
	printf("R::fun2 position:%pn", &R::fun2);
	printf("R::fun3 position:%pn", &R::fun3);

	printf("S::fun1 position:%pn", &S::fun1);
	printf("S::fun2 position:%pn", &S::fun2);

	printf("T::fun3 position:%pn", &T::fun3);
	printf("T::fun4 position:%pn", &T::fun4);

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

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

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

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

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

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

同时上面比较神奇的是,

rt = &t;

st = &t;

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

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/994423.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号