标签:
昨天去了一家学长的公司,谈了谈我的疑惑,然后做了一份题。有一些收获,想跟大家分享一下。主要是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