分类: C/C++
2011-10-05 19:57:48
浅谈对象的内存布局
作者:sccot 撰写日期:2011-09-27 ~ 2011-07-29
一:单个类的对象在内存中的布局
这个不用多说,这种情况下就只有类的成员变量占用空间。例如:
点击(此处)折叠或打开
如果在int用4个字节表示的机器上,容易知道A的大小为4。但是如果我将函数funA声明为virtual那么,(如果在此平台上指针也用4个字节来表示)A的大小就将是8。因为将函数声明为virtual的话,那么该类很可能会是基类,为了实现多态,编译器会为类加上一项成员变量,是一个指向该虚函数表的指针(常被称为vptr),所以A的大小为成员变量a的大小加上指针的大小。内存分布大致为:
图1:单个类的对象在内存中的布局
二:一般继承在内存中的布局
现在,B对象的大小是?答案是12。此时,无论将函数funB是否声明为virtual,B对象的大小都为12。由图1知B的对象包括成员变量a与b,还有就是指向虚函数表的指针,由于B从A对象继承下来了指针,所以B本身就不会再添加指针成员了。B对象内存分布大致如下:
三、多重继承在内存中的布局
现在,无论将函数funB是否声明为virtual,C对象的大小永远都是20。包括成员变量a,b,c和从A继承下来的指向A虚函数表的指针和从B继承下来的指向B虚函数的指针(因为A和B都有一个指针成员),此时,两个指针成员指向同一个位置(C的虚函数表)。C对象的内存分配大致如下:
图3:多重继承在内存中的布局
由图3可知,C有两个指针同时指向C的虚函数表,且C有虚函数funB当用B类的指针和引用调用时将调用B::funB函数,当用C类的指针和引用调用时,将调用C::funB函数。
四、虚继承在内存中的布局
现在进入最难的部分,请看下例:
现在,请问A,B,C,D对象的大小是?
第一种情况(GCC中的情况):8,16,16,28。现在B 的大小变成了16对照上面二中所说,说明在GCC中虚继承本身会添加一个指向虚函数表的指针,不管基类是否有指针已经指向了虚函数表。此时D包含了成员变量a,b,c,d和从A,B,C继承而来的三个同时指向D虚函数表的指针,所以D对象的大小为28。如果将D的申明改为class D: virtual public B, virtual public C则D的大小会变为32。此时,D在内存中的分布大致如下:
图3:虚继承在内存中的布局(情况1)
第二种情况(VC中的情况,同时也是More Effective C++作者Scott Meyers所认为的情况):8,20,20,36。第二种种情况与第一种情况不同之处在于现在在B对象和C对象内另外再加了一个指针成员,该指针成员指向虚拟基类。所以,现在B,C的大小都为20了,D从B类和C类又另外继承了两个指针,所以现在D的大小为36。此时D对象的布局大致为:
图4:虚继承在内存中的布局(情况2)
五、总结
由上面的示例可以看出,将类的成员函数声明为虚函数,以及声明继承为虚继承将造成很大的空间浪费。再加之多重继承和虚继承本身就必须在派生类中指明是使用哪一个基类的成员。所以在对空间要求很高的场景中就不太适合。这也许是Java中用接口来代替多重继承的两个原因吧。
参考文献:《More Effective C++》 Scott Meyers著