最近,从师兄那里听到一道据说是某国内知名IT公司的面试题,有关C++虚拟继承的。
- #include <iostream>
- using namespace std;
- class T {
- //public:
- // char ch;
- // static char ch;
- };
- class A : virtual public T {
- };
- class B : virtual public T {
- };
- class C : public A, public B {
- };
- class D : public A, virtual public B {
- };
- class E : virtual public A, virtual public B {
- };
- int main()
- {
- cout << sizeof(T) << endl;
- cout << sizeof(A) << endl;
- cout << sizeof(B) << endl;
- cout << sizeof(C) << endl;
- cout << sizeof(D) << endl;
- cout << sizeof(E) << endl;
- return 0;
- }
- //问输出结果?
- /*
- vc6.0
- 1
- 4
- 4
- 8
- 8
- 12
- devc++5.0, 32位,内核是gcc
- 1
- 4
- 4
- 8
- 8
- 8
- g++ 4.1, 64位
- 1
- 8
- 8
- 16
- 16
- 16
- */
- /*
- C++对象模型里面有提到的另一种情况(即没对empty virtual base class做优化),不知道是用那种C++编译器,其结果如下:
- sizeof(T) = 1
- sizeof(A) = 8
- sizeof(B) = 8
- sizeof(C) = 12
- */
之前只是了解虚拟继承是为C++多继承时,同一基类在派生层次中多次出现,引起的存储空间浪费,和二义性,而提出来的解决方案。而对具体的对象数据布局,就不是很清楚了,在VC++6.0上跑了一下这个程序,照着结果理解了半天都没找到能够同时把C(8),D(8),E(12)三个都解释得通的模型。
在网站找来一些资料,也是众说纷纭。最后下了JJHOU译的《深入探索C++对象模型》看了第3章 Data 语义学部分,讲得较为详细。对其中主要部分解释总结如下:
1. 语言本身造成的额外负担,即虚拟继承中子类中必须保存指向virtual base class subobject的指针或一个相关表格的偏移量。指针的大小跟机器有关;
2. 编译器对于特殊情况所提供的最佳化处理,virtual base class subobject会放在derived class的固定(不变动)部分尾端,即上述T的1byte大小会出现在A,B上。而有些编译器会对虚拟继承中的空基类提供优化处理,在这种策略下,一个empty virtual base class 就被视为derived class object最开头的一部分,而它并没有花费任何额外的空间,如VC++6.0里sizeof(A)=4;
3. Alignment的限制,如果不考虑对empty virtual base class进行优化,A和B大小截止到目前为5bytes。在大部分机器上,结构体大小受alignment限制,以使它们能够更有效率地在内存中被存取,上面测试的结构所使用的机器均为4bytes对齐,即会出现上述最后一种情况sizeof(A)=8。关于Alignment自己想了一个的例子如下:
- class S {
- private :
- char ach;
- int i;
- char bch;
- };//sizeof(S)=12
嘿,总之C++的对象布局,除了语言本身外,还要考虑机器,编译器的因素。这些拿来当笔试面试的题目,还真不好答。
至于sizeof(E)的大小vc6.0与devc++5.0的不同还是不清楚…晕,应该也是编译器处理策略不同引起的。
sizeof(T)为1:根据对象唯一性原则,编译器隐晦插入一个字节,以使类的不同实例,在内存中拥有唯一的地址。