C++对象模型1——类对象的sizeof、static成员、对象模型、this指针

本文详细解析了C++中类对象的sizeof计算规则,包括空类、含有虚函数的类、内存对齐和补齐机制等内容,并探讨了static成员的特点及对象模型,最后讲解了this指针的工作原理。

一、类对象的sizeof

1、空类对象的sizeof

class test3{	};

int main(int argc, char const *argv[])
{	
	test3 t;
	cout<<sizeof(t)<<endl;
	return 0;
}

C++规定,空类对象的sizeof是1,因为如果空类对象的sizeof是0,那么就意味着可以产生不占内存的代码,和计算机的原理违背。所以,编译器在编译代码时,会在test3中安插一个char数据。

向test3中添加一个函数

class test3
{
	void func() {}
};

输出结果同上,所以,类中的成员函数不占用内存空间

向test3中添加一个char成员

class test3
{
	void func() {}
	char a;
};

输出结果依然是1,将char改为int,输出结果就是4

所以,根据上面的示例,可以得出:类对象的成员函数不影响对象所占的内存空间,只有类对象的成员变量会对其所占的内存空间有影响。

 

2、含有虚函数的类对象的sizeof

class test3
{
	virtual void t3func() {}
};

int main(int argc, char const *argv[])
{	
	test3 t;
	cout<<sizeof(t)<<endl;
	return 0;
}

类对象会因为虚函数的存在增加所占的内存。这是因为:

1.每个含有虚函数的类产生出一堆指向虚函数的指针,放虚表(virtual table vtbl)中。

2.每个含有虚函数的类对象都有一个虚表指针(vptr),指向相关的虚表。虚表指针的设定和重置由每一个类的构造函数、拷贝构造函数、析构函数和operator=自动完成。此外,每一个类所独有的类型信息(用以支持RTTI)也在虚表中的第一个槽中。

所以,多出来的这8字节就是vptr的在x64平台下的sizeof

可以通过以下命令查看虚表

g++ -fdump-class-hierarchy -fsyntax-only c++model.cpp

可以看到,虚表中除了虚函数之外,还有test3的typeinfo

 

3、内存对齐和补齐

在2的基础上添加一个新的char类型

class test3
{
	virtual void t3func() {}
	char a;
};

输出结果如下

因为vptr是8字节,而char类型只有一个字节,所以编译器会进行字节填充,以保证内存对齐,提高运行效率,所以,需要补齐七个字节进行内存对齐,所以输出16

如果不想让编译器进行字节填充,可以使用如下指令

#pragma pack(1)//按照1字节内存对齐
class test3
{
	virtual void t3func() {}
	char a;
};
#pragma pack()//取消内存对齐

 

4、含有static成员的类对象的sizeof

class test3
{
	virtual void t3func() {}
	static void sfunc() {}
	static int a;
};

输出结果依然是8,所以,无论是静态成员函数还是静态成员变量都不增加类对象的大小。因为静态成员永远只存在一份实例,为所有类对象共有。静态成员位于全局数据区

总结:1、静态成员不增加类对象的sizeof。2、普通成员函数也不增加对象的sizeof。3、vtbl是基于类的,虚函数本身不增加对象的sizeof,但是编译器会在类对象内部安插一个vptr,在x64平台下增加8字节内存。如果对象中还有其他成员变量,会额外增加类对象的内存,额外增加的内存主要来自:成员变量本身的大小以及补齐内存的大小

也就是说一个类对象的大小由其非static成员的总和大小、内存填补以及因虚机制产生的额外内存组成

 

二、static成员

static成员的以下几种调用方式

class test
{
public:
	static int m_i; 	
};

int test::m_i=-1;

int main(int argc, char const *argv[])
{
	test t;
	t.m_i=0;
	test::m_i=1;
	test *pt=&t;
	pt->m_i=2;
	return 0;
}

对应的反汇编代码如下

可见,三种调用方式没有区别,汇编代码都是mov    DWORD PTR [index], value,都等价于test::m_i,因为static成员不在类对象中,因此,存取static成员也不需要使用类对象或者类对象的指针,所以,即使使用类对象或者指针进行调用,最终也会转化成作用域调用。因为static成员函数没有this,所以可以作为回调函数。static成员函数就是个没有this的全局函数

因为static成员变量或者函数并不属于类对象,所以对其取地址,得到的也只是普通的函数指针或者类型的指针,而不是成员变量或者成员函数的指针

 

三、对象模型

class myobject
{
public:
	myobject() {}; 
	virtual ~myobject() {}; 

	float getvalue() const
	{
		return value;
	}
	static int getcount()
	{
		return count;
	}
	virtual void vfunc() {}

protected:
	float value;
	static int count;
};

上述代码的对象模型大致如下

如果出现继承,模型还会不一样。上述模型也显示,当调用虚函数时,会通过vptr找到对应的vtbl,然后根据偏移地址对虚函数进行调用。

 

四、this指针

class A
{
public:
	int a;
	A()
	{
		printf("A::A() 的 this指针是:%p!\n", this);
	}
	void funA() { printf("A::funcA: this = %p\n", this); cout<<&a<<endl;}
};

class B
{
public:
	int b;
	B()
	{
		printf("B::B() 的 this指针是:%p!\n", this);
	}
	void funB() { printf("B::funcB: this = %p\n", this); cout<<&b<<endl;}
};

class C : public A, public B
{
public:
	int c;
	C()
	{
		printf("C::C() 的 this指针是:%p!\n", this);
	}
	void funC() { printf("C::funcC: this = %p\n", this); cout<<&c<<endl;}
	void funB() { printf("C::funcB: this = %p\n", this); } 
};

int main(int argc, char const *argv[])
{	
	C myc;
	myc.funA();
	myc.funB();
	myc.B::funB();
	myc.funC();
	return 0;
}

通过输出以及上述代码可知,类C的内存模型如下

其中,C的this指针与基类列表中的第一个相同(这里是A),其他基类的指针与第一个基类或者派生类的this指针相差成员变量的sizeof(这里A与C的this指针和B的this指针相差sizeof(int a))

 

参考

《深度探索C++对象模型》

《C++新经典:对象模型》

 

欢迎大家评论交流,作者水平有限,如有错误,欢迎指出

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值