上一次只是给出一个代码
http://blog.csdn.net/OpenHero/archive/2006/04/02/648005.aspx,和运行过程中的内存位置,现在具体的按照步骤分析一下C++中虚拟继承中的情况,
由于C++ stand(C++ 标准委员会) 并没有规定具体实现的办法,各个编译器厂商可能都采用不同的实现方法,
或许在不同的年代使用的方法也不相同,有可能就会得到不同的结果,这里,我采用VC7.1作为调试工具,由潜
入深的一步步分析C++ 虚继承中的内存布局;
这一部分由Empty virtual base calss (空虚基类:空的calss)入手,查看VC7.1中是如何实现虚拟继承的.
下面给出测试的代码:
一,菱形的继承模式
class A
{ };
class B : virtual public A
{
};
class C : virtual public A
{
};
class D : public C, public B
{
};
int _tmain(int argc, _TCHAR* argv[])
{
A a;
B b;
C c;
D d;
int *pd = (int*)&d;
int *pd_c = (int*)(C*)(&d);
int *pd_b = (int*)(B*)(&d);
int *pd_a = (int*)(A*)(&d);
cout<< sizeof(a) << endl;
cout<< sizeof(b) << endl;
cout<< sizeof(c) << endl;
cout<< sizeof(d) << endl;
cout<< pd << endl;
cout<< pd_c << endl;
cout<< pd_b << endl;
cout<< pd_a << endl;
return 0;
}
有人会问,
class A
{ };
A a, 其中的a会有大小嘛?当然,a里面什么都没有,姑且就认为它是空的吧,但是,编译器如何才能记住a,而又
是空?你能想到一种实现的方法嘛?所以,编译器为了记住空的a,在a的内部放入了一个char的标记,作为它的
唯一标识,这样编译器才能认识那个a. 如果你有兴趣,你可以尝试定义A a,b; 然后你看看a和b是否是一
样的?现阶段来说,应该是不一样的,或许以后谁会发明新的实现方式,那个时候或许是一样也有可能了。
现在让我们看看上面程序的输出情况,如下:
1
4
4
8
0012FEAC
0012FEAC
0012FEB0
0012FEB4
这里我先不对上面的输出做出解释,或许你看了这样的输出会有疑惑,是不是和你想象的不一样?带着疑问
继续往下面看。
下面我再给出两段代码,同时给出他们各自的输出,然后再对这些输出做出解释:
二,六边形(菱形一)
class A
{ };
class B : virtual public A
{
};
class C : virtual public A
{
};
class D : /*virtual */public B
{
};
class E : /*virtual */public C
{
};
class F : public E, public D
{
};
int _tmain(int argc, _TCHAR* argv[])
{
A a;
B b;
C c;
D d;
E e;
F f;
int *pf = (int*)&f;
int *pf_e = (int*)(E*)(&f);
int *pf_d = (int*)(D*)(&f);
int *pf_c = (int*)(C*)(&f);
int *pf_b = (int*)(B*)(&f);
int *pf_a = (int*)(A*)(&f);
cout<< sizeof(a) << endl;
cout<< sizeof(b) << endl;
cout<< sizeof(c) << endl;
cout<< sizeof(d) << endl;
cout<< sizeof(f) << endl;
cout<< pf << endl;
cout<< pf_e << endl;
cout<< pf_d << endl;
cout<< pf_c << endl;
cout<< pf_b << endl;
cout<< pf_a << endl;
return 0;
}
输出结果:
1
4
4
4
4
8
0012FE94
0012FE94
0012FE98
0012FE94
0012FE98
0012FE9C
三,六边形(菱形二)
class A
{ };
class B : virtual public A
{
};
class C : virtual public A
{
};
class D : virtual public B
{
};
class E : virtual public C
{
};
class F : public E, public D
{
};
int _tmain(int argc, _TCHAR* argv[])
{
A a;
B b;
C c;
D d;
E e;
F f;
int *pf = (int*)&f;
int *pf_e = (int*)(E*)(&f);
int *pf_d = (int*)(D*)(&f);
int *pf_c = (int*)(C*)(&f);
int *pf_b = (int*)(B*)(&f);
int *pf_a = (int*)(A*)(&f);
cout<< sizeof(a) << endl;
cout<< sizeof(b) << endl;
cout<< sizeof(c) << endl;
cout<< sizeof(d) << endl;
cout<< sizeof(e) << endl;
cout<< sizeof(f) << endl;
cout<< pf << endl;
cout<< pf_e << endl;
cout<< pf_d << endl;
cout<< pf_c << endl;
cout<< pf_b << endl;
cout<< pf_a << endl;
return 0;
}
输出结果:
1
4
4
8
8
16
0012FE84
0012FE84
0012FE88
0012FE8C
0012FE90
0012FE8C
下面对其中的一,二做分析,三是一个有趣的现象,你可以考虑考虑:)
可以看到,只要有virtual的地方,都多出4个字节,用来干嘛?
在VC的编译器中,采用了virtual base calss pointer的方式来实现虚继承,从而保证子类回溯到基类的时
候只存在一个实体。多出来的4个字节就是用来存放各自的指向virtual base class 的地址的。
你可能又要问,a的大小为1,b,c……的大小只是4个4个增加,考虑内存对其,那不是会更大吗?
看到前面提到的一个问题了吗?
编译器只是为了记住a的时候才把a标示出来,如果知道a是空的,它既然有了子类,子类表示出来的时候,
当然基类就存在其中了:)
你可能又问? 那为什么不把子类也表示为1,不就行了吗?当然不行,就像上面提到的,因为是为了实现虚
继承的菱形一样的继承样式,在子类中只存在基类一个实体,所以,就才用了指针的方式,而不是把整个实
体放到子类的内存中。
由于C++ stand(C++ 标准委员会) 并没有规定具体实现的办法,各个编译器厂商可能都采用不同的实现方法,
或许在不同的年代使用的方法也不相同,有可能就会得到不同的结果,这里,我采用VC7.1作为调试工具,由潜
入深的一步步分析C++ 虚继承中的内存布局;
这一部分由Empty virtual base calss (空虚基类:空的calss)入手,查看VC7.1中是如何实现虚拟继承的.
下面给出测试的代码:
一,菱形的继承模式
class A
{ };
class B : virtual public A
{
};
class C : virtual public A
{
};
class D : public C, public B
{
};
int _tmain(int argc, _TCHAR* argv[])
{
A a;
B b;
C c;
D d;
int *pd = (int*)&d;
int *pd_c = (int*)(C*)(&d);
int *pd_b = (int*)(B*)(&d);
int *pd_a = (int*)(A*)(&d);
cout<< sizeof(a) << endl;
cout<< sizeof(b) << endl;
cout<< sizeof(c) << endl;
cout<< sizeof(d) << endl;
cout<< pd << endl;
cout<< pd_c << endl;
cout<< pd_b << endl;
cout<< pd_a << endl;
return 0;
}
有人会问,
class A
{ };
A a, 其中的a会有大小嘛?当然,a里面什么都没有,姑且就认为它是空的吧,但是,编译器如何才能记住a,而又
是空?你能想到一种实现的方法嘛?所以,编译器为了记住空的a,在a的内部放入了一个char的标记,作为它的
唯一标识,这样编译器才能认识那个a. 如果你有兴趣,你可以尝试定义A a,b; 然后你看看a和b是否是一
样的?现阶段来说,应该是不一样的,或许以后谁会发明新的实现方式,那个时候或许是一样也有可能了。
现在让我们看看上面程序的输出情况,如下:
1
4
4
8
0012FEAC
0012FEAC
0012FEB0
0012FEB4
这里我先不对上面的输出做出解释,或许你看了这样的输出会有疑惑,是不是和你想象的不一样?带着疑问
继续往下面看。
下面我再给出两段代码,同时给出他们各自的输出,然后再对这些输出做出解释:
二,六边形(菱形一)
class A
{ };
class B : virtual public A
{
};
class C : virtual public A
{
};
class D : /*virtual */public B
{
};
class E : /*virtual */public C
{
};
class F : public E, public D
{
};
int _tmain(int argc, _TCHAR* argv[])
{
A a;
B b;
C c;
D d;
E e;
F f;
int *pf = (int*)&f;
int *pf_e = (int*)(E*)(&f);
int *pf_d = (int*)(D*)(&f);
int *pf_c = (int*)(C*)(&f);
int *pf_b = (int*)(B*)(&f);
int *pf_a = (int*)(A*)(&f);
cout<< sizeof(a) << endl;
cout<< sizeof(b) << endl;
cout<< sizeof(c) << endl;
cout<< sizeof(d) << endl;
cout<< sizeof(f) << endl;
cout<< pf << endl;
cout<< pf_e << endl;
cout<< pf_d << endl;
cout<< pf_c << endl;
cout<< pf_b << endl;
cout<< pf_a << endl;
return 0;
}
输出结果:
1
4
4
4
4
8
0012FE94
0012FE94
0012FE98
0012FE94
0012FE98
0012FE9C
三,六边形(菱形二)
class A
{ };
class B : virtual public A
{
};
class C : virtual public A
{
};
class D : virtual public B
{
};
class E : virtual public C
{
};
class F : public E, public D
{
};
int _tmain(int argc, _TCHAR* argv[])
{
A a;
B b;
C c;
D d;
E e;
F f;
int *pf = (int*)&f;
int *pf_e = (int*)(E*)(&f);
int *pf_d = (int*)(D*)(&f);
int *pf_c = (int*)(C*)(&f);
int *pf_b = (int*)(B*)(&f);
int *pf_a = (int*)(A*)(&f);
cout<< sizeof(a) << endl;
cout<< sizeof(b) << endl;
cout<< sizeof(c) << endl;
cout<< sizeof(d) << endl;
cout<< sizeof(e) << endl;
cout<< sizeof(f) << endl;
cout<< pf << endl;
cout<< pf_e << endl;
cout<< pf_d << endl;
cout<< pf_c << endl;
cout<< pf_b << endl;
cout<< pf_a << endl;
return 0;
}
输出结果:
1
4
4
8
8
16
0012FE84
0012FE84
0012FE88
0012FE8C
0012FE90
0012FE8C
下面对其中的一,二做分析,三是一个有趣的现象,你可以考虑考虑:)
可以看到,只要有virtual的地方,都多出4个字节,用来干嘛?
在VC的编译器中,采用了virtual base calss pointer的方式来实现虚继承,从而保证子类回溯到基类的时
候只存在一个实体。多出来的4个字节就是用来存放各自的指向virtual base class 的地址的。
你可能又要问,a的大小为1,b,c……的大小只是4个4个增加,考虑内存对其,那不是会更大吗?
看到前面提到的一个问题了吗?
编译器只是为了记住a的时候才把a标示出来,如果知道a是空的,它既然有了子类,子类表示出来的时候,
当然基类就存在其中了:)
你可能又问? 那为什么不把子类也表示为1,不就行了吗?当然不行,就像上面提到的,因为是为了实现虚
继承的菱形一样的继承样式,在子类中只存在基类一个实体,所以,就才用了指针的方式,而不是把整个实
体放到子类的内存中。