发布时间:2011-12-5 23:36
分类名称:C++
The Semantics of Data
class X { };
class Y : public virtual X { };
class Z : public virtual X { };
class A : public Y, public Z { };
sizeof(X) = ?
sizeof(Y) = ?
sizeof(Z) = ?
sizeof(A) = ?
你会发现,不同机器上,不同的编译器里,可能结果都会不一样。
通常情况下,X的大小应该在都是一样的,大小为1byte,这是被编译器安插进去的一个char,这样才使得这个class的object拥有独一无二的地址。
Y和Z的大小会依据下面三种情况而定:
而其他编译器可能不插入额外指针,而是通过其他机制。
微软的就是这么处理的。
下面安装4字节对齐的方式来分析:
sizeof(X) = 1
sizeof(Y) = 1 + 4 + 3 = 8 // 为何加3?因为要按照4字节的对齐1+4=5, 加3为8,正好能被4整除
sizeof(Z) = 1 + 4 + 3 = 8
sizeof(A) = 1 + 4 + 4 + 3 = 12
sizeof(X) = 1
sizeof(Y) = 4
sizeof(Z) = 4
sizeof(A) = 4 + 4 = 8
Data Member 的布局
C++Standard要求,同一个access section(private,public,protected)中,member排列只要符合"较晚出现的member在class object中有较高的地址。没有要求一定要连续。而且允许那些从内部产生处理的members(如vptr)自由放在任何位置上。(有的编译器vptr在所有member之后,有的则在所有member之前)。
目前,各家编译器都是把一个以上的access section连锁成一个,依照声明次序,成为一个连续区域。
Data Member的存取
Point3d origin;
origin.x = 0.0;
x的存取成本是什么?分几种情况考虑:
继承
Derived class object = itself members + base class(es) member
至于它们的排列次序,C++ standard没有规定,可以自由安排。大部分编译器都是base class members在前,derived的在后。 virtual base class除外。
单一继承(没有多态)
下面是关于一个字节补位与继承的问题
class Concrete1
{
private:
int val;
char bit1;
}
class Concrete2 : public Concrete1
{
private:
char bit2;
}
class Concrete3 : public Concrete2
{
private:
char bit3;
}
为什么字节补位也被继承下来了?看看下图:
Concrete2 *pc2;
Concrete1 *pc1_1, *pc1_2;
pc1_1 = pc2;
*pc1_2 = *pc1_1; //就发生了上图的情况。
单一继承(有多态)
多态带来的代价:
vptr被放在Class的尾端
Vptr被放在Class的前端 (微软的编译就是放在前端,低字节处)
vptr被放在了base class的尾端
Point 3d;
Point *p2d = &3d;
单继承下,基本上指针无需调整,但vptr在前端例外,如果Base class没有虚函数而其子类有虚函数,指针需要加一个vptr的长度。
因为这个vptr是子类安插的,属于其子类。使用微软编译器打印出来俩种单一继承的vptr:
1. 从父类继承来的vptr:
class CTestD size(12):
+---
| +--- (base class CTest) // 这是父类范围
0 | | {vfptr} // vfptr 在父类范围内
4 | | a
| +---
8 | m_n
+---
CTestD::$vftable@:
| &CTestD_meta
| 0
0 | &CTestD::{dtor}
1 | &CTestD::somefunc
2. 父类没有虚函数,但其子类有:
class CTestD size(12):
+---
0 | {vfptr} // vfptr在子类范围
| +--- (base class CTest) // 父类范围,如果将子类实例地址赋值给父类指针,指针首先需要加一个vptr长度。
4 | | a
| +---
8 | m_n
+---
CTestD::$vftable@:
| &CTestD_meta
| 0
0 | &CTestD::somefunc
多重继承
多重继承的问题出现在derived class objects和其第二个和后继的base class objects直接的转换。
Vertex3d v3d;
Vertex *pv;
Point2d *p2d;
Point3d *p3d;
pv = &v3d;
转换为:
pv = pv3d ? (vertex *)((char *)&v3d + sizeof(Point3d)) : 0;
而:
p2d = &v3d;
p3d = &v3d;
无需转换。(Point2d中有virtual functions,如果没有,在vptr前置的情况系啊,可能需要进行移位)
下图是几个class对象的布局:
class Point2d
{
public:
int _x;
int _y;
virtual void test2d()
{
}
};
class Point3d : public Point2d
{
public:
int _z;
};
class Vertex
{
public:
Vertex *_next;
virtual void testVertex()
{
}
};
class Vertex3d :
public Point3d,
public Vertex
{
public:
int mumble;
};
VS2008布局如下:
class Vertex3d size(28):
+---
| +--- (base class Point3d)
| | +--- (base class Point2d)
0 | | | {vfptr} // 从Pointer2d继承下的vfptr
4 | | | _x
8 | | | _y
| | +---
12 | | _z
| +---
| +--- (base class Vertex)
16 | | {vfptr} // 从Vertex继承下的vfptr
20 | | _next
| +---
24 | mumble
+---
Vertex3d::$vftable@Point3d@:
| &Vertex3d_meta
| 0
0 | &Point2d::test2d
Vertex3d::$vftable@Vertex@:
| -16
0 | &Vertex::testVertex
所以,非虚继承的多继承下,子类存储着多个vptr,每一条单条的继承,都能继承下vptr。也就对应多张虚表。因为多个父类不能共用一张虚表,调整起来,会更加混乱。
虚拟继承
为何需要虚拟继承?看下图。
如果 在iostream中引用ios中的成员,编译器就会因为无法判断到底引用的是istream的ios,还是ostream的ios。(当然可以使用作用符强制指定,但子类可能并不需要知道太多细节)如果为虚继承,ios的实体则为一个。
编译器一般将含有virtual base class的subobjects(如iostream)分割为两部分,一部分不变,一部分共享(ios)。不变部分拥有固定的offset。而共享部分会随着每次的派生操作而发生变化。cfont编译器实现上,会为每一个derived class object安插一些指针,来指向virtual base class,来存取virtual base class members。
Point2d *p2d = pv3d; 转换为: Point2d *p2d = pv3d ? pv3d->__vbcPoint2d : 0;
微软的实现方式为产生virtual base class table,每个class object如果有一个或者多个virtual base classes,编译器就会安插一个指针,指向virtual base class table,table里面存放了真正的virtual base class指针。
下面一段代码,在微软编译器中表现的内存如下:
class Base
{
public:
int base;
virtual void fooBase() {}
};
class A : virtual public Base
{
public:
int a;
virtual void fooA() {}
};
class B : virtual public Base
{
public:
int b;
virtual void fooB() {}
};
class Derived : public A, public B
{
public:
int d;
virtual void fooD() {}
};
布局:
1>class Derived size(36):
1> +---
1> | +--- (base class A)
1> 0 | | {vfptr} // A的虚函数指针,指向virtual funtions table
1> 4 | | {vbptr} // A的虚父类指针,指向virtual base class table
1> 8 | | a
1> | +---
1> | +--- (base class B)
1>12 | | {vfptr} // B的虚函数指针,指向virtual funtions table
1>16 | | {vbptr} // B的虚父类指针,指向virtual base class table
1>20 | | b
1> | +---
1>24 | d
1> +--- // 到此为止,都是不变的部分
1> +--- (virtual base Base)
1>28 | {vfptr} // Base的虚函数指针,指向virtual funtions table
1>32 | base
1> +--- // 虚基类,可变部分
1>Derived::$vftable@A@: // A的虚函数表
1> | &Derived_meta
1> | 0 // offset(Derived,A) = 0
1> 0 | &A::fooA
1> 1 | &Derived::fooD
1>Derived::$vftable@B@: // B的虚函数表
1> | -12 // offset(Derived,B) = 12
1> 0 | &B::fooB
1>Derived::$vbtable@A@: // A的虚基类表
1> 0 | -4 // offset(A,A.vbptr) = 4
1> 1 | 24 (Derivedd(A+4)Base)
1>Derived::$vbtable@B@: // B的虚基类表
1> 0 | -4 // offset(B,B.vbptr) = 4
1> 1 | 12 (Derivedd(B+4)Base)
1>Derived::$vftable@Base@: // Base的虚函数表
1> | -28 // offset(Derived,Base) = 0
1> 0 | &Base::fooBase
1>Derived::fooD this adjustor: 0
1>vbi: class offset o.vbptr o.vbte fVtorDisp
1> Base 28 4 4 0