今天看到一篇文章,上面提到了一个很有意思的问题,拿来跟大家分享一下
开门见山,下面是几个class的定义
class A
{
};
class B:virtual public A
{
};
class C:virtual public A
{
};
class D:public B, public C
{
};
int main()
{
cout<<sizeof(A)<<endl<<sizeof(B)<<endl<<sizeof(C)<<endl<<sizeof(D)<<endl;
system("pause");
return 0;
}
请问程序的输出是什么?
感兴趣的可以自己试一下,我在这里直接贴出结果,我在GCC下编译得出的结果分别是:
1
8
8
16
在微软的visual studio 2010上编译得出的结果是:
1
4
4
8
程序的结果跟编译器有关系,说不定你用的另一版本的编译器得到的结果跟上面的两种都不同.
下面来分析一下这两种情况下的结果,如果有什么不对的地方请批评指正
先来看下第一个sizeof(A),两种情况都是1个字节,A是个空的class,为什么空的class还占一个字节,这不是在浪费memory吗?其实不然, 我们知道一个类的多个实例或者说对象都要占有一定的内存空间,这是实例化时调用构造函数后分配的, 如果A的实例不占有内存空间,也就是sizeof(A) 等于0,那么各个对象在内存中怎么区分呢,操作系统凭什么定位到各个对象呢?基于此原因, 编译器为每个空类的对象分配一个字节,这样使得浪费最少。扩展一下,如果A像下面这样定义
class A
{
Int a;
};
那么sizeof(A)是不是就等于一个int的大小(32位系统下是4个字节)再加上A为空时的一个字节,得到的结果为4+1=5,如果考虑aliment的话结果为8呢?非也,既然A已经有一个Nonstatic data member,在内存中就要占用一定的内存,那么编译器就不用再自作多情分配一个字节给A来加以区分。
下面看下第二行的输出,两种编译器得到的结果不一样,一个sizeof(B)为8一个sizeof(B)为4,事实上,B和C的大小主要受下面几个因素的影响:
1. 语言本身为了支持vitual base class而带来的额外的负担,我们很熟悉在C++中,为了支持虚函数等,产生了vptr, vtbl等额外的负担,这里也一样,在B和C中这种负担反应在一个指针上,这个指针可能指向A的子对象,也可能指向一个表格,这个表格包含了A的子对象的地址或者其偏移量。
2. 编译器对于特殊情况所提供的优化处理, 这里A的1字节大小也出现在其子类B和C身上,一般这一个字节会放在B和C对象的尾部.但是某些编译器会对空的虚基类提供特殊的支持,这个特殊的支持的具体做法就是将一个空的虚基类看做是其子类对象的最开始的一部分,也就是说它并没有花费额外的空间,换句话说就是B和C的对象里没有A的那1个字节的开销,那么在这里B和C的大小就变成4个字节,这四个字节大小就是那个指向虚基类的指针.根据上面的输出,很显然,微软的visual studio 2010提供了对空的虚基类的特殊支持,而gcc没有,所以gcc的输出B和C的大小为
指针的大小+A的1字节+字节对齐=8个字节
下面是gcc编译器中这几个类的结构:
Class B和class C对象的结构
4个字节 指向class A的指针 |
Class A中的一个字节 |
为了字节对齐填充的3个字节 |
class A(一个字节)
下面是微软的编译器中这几个类的结构:
Class B和class C对象的结构
4个字节 指向class A的指针 |
Class B和class C已经分析完了,下面来看看class D的结构又是如何?
Class D派生自 B和C,而B和C的大小跟编译器有关系,那么class D必然跟编译器的种类也存在着关系,似乎是废话,^_^.这里分析方法跟上面的class B和class c一样,要看编译器对空的虚基类是否做了特殊处理
参考资料:《深度探索c++对象模型》