标签:
昨天去了一家学长的公司,谈了谈我的疑惑,然后做了一份题。有一些收获,想跟大家分享一下。主要是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问题就到这里了,以小见大,这些细枝末节其实才是最能体现一个人的功力的地方。希望各位同学共勉。
标签:
原文地址:http://blog.csdn.net/jxlaozhuan/article/details/51362552