在判断一个C++对象大小时,并不能直接根据字面意义上的数据进行简单的叠加,因为除了这些数据,编译器还额外做了很多工作,我们需要注意如下几点。
1. 对于空对象的判断,有如下代码:
class A
{
};
int main(void)
{
A* p = new A;
cout<<"A="<<sizeof(*p)<<endl;
}
输出结果是:
A=1
很奇怪,为什么一个空对象大小为1.
对象的创建过程:1.分配空间 2. 调用构造函数对空间初始化 3.返回对象指针
如果对象大小为0,那么连空间都无法申请。对于定义为空的类,为了能够创建对象,编译器会安插一个char数据,用以表示不同的A对象。
可以测试,空类的直接继承子类,大小也为1,对空类的多重继承,大小也为1
2. 虚函数指针占用大小
2.1 若类中没有虚函数,编译器不会为其生成虚表指针。
class A
{
int a;
};
class F : A
{
int f;
};
A=4 F=8
可见其中并没有虚表指针。
2.2 当类中有虚函数或者父类中有虚函数时,系统会生成虚表指针指向虚表,虚表不占用对象空间,但是虚表指针会。
class B
{
int b;
virtual void func0() {};
};
sizeof(B) = sizeof(vptr) + sizeof(int) = 8;
单一继承,只会有一个虚表指针,但是如果有多重继承,会有多个虚表指针,比如:
class B
{
int b;
virtual void func0(){}
};
class C
{
char ch1;
char ch2;
virtual void func() { }
virtual void func1() { }
};
class E: public B, public C
{
int e;
virtual void func0() { }
virtual void func1() { }
};
E的结构:
多重继承下 大小SIZEOF(B) + sizeof(C) + e = 8 + 8 + 4 = 20
在此处编写了相关测试程序
int _tmain(int argc, _TCHAR* argv[])
{
E* e = new E();
printf("%x\n", e);
printf("%x\n", &e->b);
printf("%x\n", &e->ch1);
typedef void (*Fun)();
Fun p = (Fun)*(int*)*(int*)e;
p();
printf("%x\n", &e->ch1 - 4);
printf("%x\n", &e->ch1);
p = (Fun)*(int*)*(int*)(&e->ch1 - 4);
p();
getchar();
return 1;
}
测试结果:
Fun p = (Fun)*(int*)*(int*)e;
p();
printf("%x\n", &e->ch1 - 4);
p();
可以发现,第二个虚表指针介于b(1a44ac)与ch1(1a44b4),发现正好有四个字节的空闲空间,这个空间就是用于存放第二个虚表指针。
多重继承有如下需要注意的问题:
子类中增加虚函数,有两个虚函数表,函数指针放在主基类的虚表中
class A
{
public:
int a;
virtual void func0(){cout<<"A: func0"<<endl;};
};
class B
{
public:
int b;
virtual void func0() {cout<<"B: func0"<<endl;};
};
class C : public A, public B
{
public:
char ch1;
char ch2;
};
int main()
{
C* c = new C;
c->func0();
}
编译报错:
error C2385: 对“func0”的访问不明确
1> 可能是“func0”(位于基“A”中)
1> 也可能是“func0”(位于基“B”中)
因为无法确定调用基类A中的函数还是调用B中的函数。若在C中实现函数func0(),则OK。
子类中增加虚函数,有两个虚函数表,函数指针放在主基类的虚表中
虚表更新:上例中A,B中都有方法func0(),如果C中也实现了方法func0(),那么A,B中的虚表都会将虚表中的方法更新为
C::func0();
C::func0();