码迷,mamicode.com
首页 > 编程语言 > 详细

【C++】C++对象模型的sizeof问题

时间:2016-05-12 17:28:41      阅读:251      评论:0      收藏:0      [点我收藏+]

标签:

昨天去了一家学长的公司,谈了谈我的疑惑,然后做了一份题。有一些收获,想跟大家分享一下。主要是C++对象的sizeof问题。


先上代码

#include "stdafx.h"
#include <stdio.h>

class A {};

class B {
	int _num;
};

class C {
	static int c_num;
};

class D {
public:
	virtual ~D(){};
	int _num;
};

class E : public D {
	char _char;	
};

class F : virtual public A {};

class G : virtual public F {};

class H : virtual public A {
	int _num;
};

class I : virtual public H {};

int main(int argc, char* argv[])
{
	printf("Size of A is %d\n", sizeof(A));
	printf("Size of B is %d\n", sizeof(B));
	printf("Size of C is %d\n", sizeof(C));
	printf("Size of D is %d\n", sizeof(D));
	printf("Size of E is %d\n", sizeof(E));
	printf("Size of F is %d\n", sizeof(F));
	printf("Size of G is %d\n", sizeof(G));
	printf("Size of H is %d\n", sizeof(H));
	printf("Size of I is %d\n", sizeof(I));
	return 0;
}
请问结果是怎样的呢?

这里我在A和C的判断上出了问题,我将A和C都是写了4,但是结果都是1。下面是运行结果截图:


技术分享

查了资料后发现,原来A之所以是1不是0的原因是因为C++中的一条规定:每个对象的地址都应该是独一无二的。这句话怎么理解呢,比如你A a[10],就有10个A类型的对象了,如果sizeof(A) = 0的话,那这10个对象的地址不就都重合了么。之所以不为4呢,也是考虑到空对象既然没有东西,就尽量少占用资源,所以就只给它分配了一个字节。

至于C,static变量是所有该类型的对象通用的,所以c_num是存在静态变量区的,在sizeof(C)的计算时,并不会把c_num的大小计算进去,所以C跟A的sizeof其实是等价的。

B因为int占4个字节,所以size为4很好理解。D是有一个虚函数指针,一个int值,指针4个字节int4个字节,一共为8也很好理解。哦对了,虚函数指针是如果你的类存在虚函数,编译器就会帮你构造一个表,这个表里存的都是虚函数,而类内就会多出来一个隐藏的指针,这个指针指向虚函数表的表头。为了理解,我举个例子。

class FooOfVirtual {
public:
	int _num;
	virtual void foo1();
	virtual void foo2();
	virtual void foo3();
	virtual void foo4();
	virtual void foo5();
};
这么多虚函数,FooOfVirtual的size依然为8。这样一来就理解了吧?

那么E为什么为12呢,这涉及到字节对齐的问题。字节对齐就是说,如果不按照适合其平台要求对 数据存放进行对齐,会在存取效率上带来损失。比如有些平台每次读都是从偶地址开始,如果一个int型(假设为32位系统)如果存放在偶地址开始的地方,那 么一个读周期就可以读出这32bit,而如果存放在奇地址开始的地方,就需要2个读周期,并对两次读出的结果的高低字节进行拼凑才能得到该32bit数据。更详细的可以去网上查阅资料理解。

我们来分析一下为何E为12,首先E继承自D,D为8,char为1字节。但是别忘了字节对齐,默认情况是读取变量中size最大的成员的字节数来对齐的,也就是int的4(当然虚函数指针也为4).这么一来,char虽然是1个字节的变量,但是会自动后面补上 00 00 00,也就成了4个字节,共12个字节。

针对上面,我给出一个例子:

class Format1 {
	char a;
};

class Format2 {
	char a;
	char b;
};

class Format3 {
	char a;
	short b;
};


这三个类分别size为多少呢?结果分别是 1 2 4。为什么呢?第一个和第二个char型是size最大的,所以有效对齐值为1,2。第三个是short最大,所以有效对齐值为2,,2*2=4。还有,如果你想手动设置对齐值,那么看下面一个例子

class Format1 {
	char a;
	int b;
};

#pragma pack(push)
#pragma pack(1)

class Format2 {
	char a;
	int b;
};

#pragma pack(pop)

1和2的size分别为8  5。这是因为在Format2中,通过语句#pragma pack(1)将对齐值统一设置为了1.对4字节长度的int型变量来说,4%1 = 0;符合,char   有 1%1 =0符合。故不进行对齐操作。所以size为5。至于语句

#pragma pack(push)

#pragma pack(pop)

这两句前者是对齐值压栈,后者是对齐值出栈。类似于汇编的保护现场,很简单。这里再贴上一点关于有效对齐值优先取值的东西吧:

1.数据类型自身的对齐值:

对于char型数据,其自身对齐值为1,对于short型为2,对于int,float类型,其自身对齐值为4,对于double型,其自身对齐值为8,单位字节。 

2.结构体或者类的自身对齐值:其成员中自身对齐值最大的那个值。  

3.指定对齐值:#pragma pack (value)时的指定对齐值value。  

4.数据成员、结构体和类的有效对齐值:自身对齐值和指定对齐值中小的那个值。


好啦,继续回到我们的主题,现在是F,G,H,I的解释环节。可以这么理解,每虚继承一个类,当前类的大小有如下公式:

原来本身大小(为空时算0) + 4(指向虚继承的类的一个指针) + 虚继承的类的大小(为空时算0)  =  当前大小。

这样一来,是不是就能了解上面的size了?但是,但是!!来看一个例子:

#include "stdafx.h"
#include <stdio.h>

class A {};

class B : virtual public A {
	char b_num;
};

class C : virtual public A {
	char c_num;
};

class D : virtual public B, virtual public C {};

int main(int argc, char* argv[])
{
	
	printf("%d\n",sizeof(D));
	return 0;
}
请问,D为?答案是20。这时候你是不是想,诶这个公式是错误的吧?其实不然,这涉及到虚基类的概念。详细的可以参考这篇博文:这里是入口

也就是说A作为一个又被B又被C虚继承的基类,它在D中只有一个指针,也就是只占用一次4字节,这样一来,是不是公式又正确了呢。

另外要注意的是,虚继承跟继承的差别很大的哦,具体可以看这篇博文:这里是入口


好了,这次的C++对象模型的sizeof问题就到这里了,以小见大,这些细枝末节其实才是最能体现一个人的功力的地方。希望各位同学共勉。

【C++】C++对象模型的sizeof问题

标签:

原文地址:http://blog.csdn.net/jxlaozhuan/article/details/51362552

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!