class MyClass
{
};
对于上面的这个类,大家可否很明确的告诉我,sizeof(MyClass)
是多少?
我在Visual Studio 2017 和 Gcc7.2上得到的输出都是 1。
大家是否会很好奇,为什么明明是个空类,却还是会占用一个字节的空间呢?
事实上对待一个空类,几乎所有编译器的做法都是安插一个隐藏的一字节(char)进去。如果不这么做的话,这个空类的两个object如果连续的声明在一起的话,他们会被标识为同一地址。所以安插一个一字节的char进去之后,这两个object就分别拥有了独一无二的内存地址了。
即使存在构造函数和析构函数(非虚)也不影响其1字节的大小,因为构造函数和析构函数在调用的时候,只需要知道其地址即可,不需要占用object的内存大小。
class X{};
class Y:public X{};
class Z:public X{};
class A:public Y,public Z{};
上述的情况,sizeof(X),sizeof(Y),sizeof(Z),sizeof(A) 都是1,因为继承并没有带来额外的开销,每一个class 的object都是空的,所以编译器也都只会安插进去1字节的char。
class X{};
class Y:virtual public X{};
class Z:virtual public X{};
class A:public Y,public Z{};
再看看这个变体呢? 在在visual studio x86选项下面 sizeof 分别是 1,4,4,8
在解释这些大小的原因之前,我们先看看到底有哪些因素会影响Y,Z,A的大小。
- 语言本身所造成的额外负担
当语言支持 virtual base class时,就会导致一些额外的负担。
在drive class 中,这个额外的负担反映在某种形式的指针身上,它或者指向virtual base class subobject或者指向一个相关表格,这个表格中要么是存放virtual base class subobject的地址,要么就是存放其偏移位置。 - 编译器对于特殊情况所提供的优化处理
Virtual base class X subobject中被插入的1bytes大小也出现在 class Y和Z身上。传统上它被放在 drived class 的固定部分(不变动)的尾部。
某些编译器会对empty virtual base class提供特殊的优化。 - 字节对齐的限制
在32为系统上,如果编译器没有优化掉virtual base class中插入的1bytes的话,现在Y,Z的大小应该是5bytes。大部分编译器会将其对齐到机器字节大小的倍数,以便能在内存中进行有效的存取。所以经过字节对齐后的大小,应该是在8bytes。
因为在32位机器上,默认的alignment是4bytes,所有Y,Z后面必须填补3bytes。
Empty virtual base class 已经成为了 C++ OO设计的一个特有术语了。它提供一个 virtual interface,没有定义任何数据。很多现代编译器都对此提供了特殊的处理。在这种策略下,就没有必要在一个drived class中安插一个char,因为已经有指向virtual base class 的指针了。
所以上图可以变为如下形式(Visual C++ 编译器):
编译器之间的潜在差异正说明了C++对象模型的演化。这个模型为一般情况提供了解决之道。当出现特殊情况时,种种启发法(尝试优化)被引入,提供优化处理。如果实验成功,启发法就提升为编译器器普遍的策略,并跨越编译器合并。它被视为标准(虽然并不规范为标准),久而久之也就成了语言的一部分。
virtual function table 和 之前讲过的NRV都是例子。
那么我们继续看看,class A,差点把这老哥忘记了。
如果Y,Z不是虚继承自class X的话,在知道Y,Z的大小都是8之后我们可能会认为是16。不过我们这里讨论的情况是虚继承。
这里有一点你需要知道,一个 virtual base class subobject 只会在 drived class 中存在一份实例,不管它在class 继承体系中出现多少次。
class A的大小由以下几点决定:
- 被大家所恭喜的唯一一个class X实例,大小为1byte。
- Base class Y的大小,减去“因 virtual base class X而配置”的大小,结果是4 bytes。同理Z也是4,所以加起来是8 bytes。
- class A自己的大小: 0 byte。
- class A的alignment。前三项的总和,表示调整前的大小是9 bytes。class A 必须调整至 4 bytes的整数倍。所以需要补 3bytes,结果是12 bytes。
不过这个大小,你现在自己应该很难得到,因为现在编译器优化都很nice,请继续往下看。
现在的大多数编译器都会优化掉 class X的那 1 byte,所以最后为了字节对齐而填补的 3 bytes 也不需要了。那么的到的大小应该为8。
C++ standard 并不强制规定如 “base class subobject的排列顺序”或“不同权限限定的 data member的排列顺序”这种琐碎的细节。它也不规定 virtual function 或 virtual base class 的实现细节。
C++ standard只说:那些细节由编译器厂商自己制定。