一个C++空类实例化大小事实上并不空,它有一个隐晦的1个byte.
首先:何为类的实例化?
所谓类的实例化,就是在内存中分配一块地址。
例一:
#include<iostream>
using namespace std;
class A {};
class B {};
class C : public A {
virtual void c_fun()=0;
};
class D : public B, public C {};
int main()
{
cout<<"sizeof(A):"<<sizeof(A)<<endl;
cout<<"sizeof(B):"<<sizeof(B)<<endl;
cout<<"sizeof(C):"<<sizeof(C)<<endl;
cout<<"sizeof(D):"<<sizeof(D)<<endl;
return 0;
}
输出:
sizeof(A):1
sizeof(B):1
sizeof(C):4
sizeof(D):8
请按任意键继续. . .
为什么会出现这种结果呢?
类A,B明明是空类,它的大小应该为为 0,为什么 编译器输出的结果为1呢?这就是我们刚才所说的实例化的原因(空类同样可以被实例化),每个实例在内存中都有一个独一无二的地址,为了达到这个目的,编译器往往会给一个空类隐含的加一个字节,这样空类在实例化后在内存得到了独一无二的地址.所以A,B的大小为1.
类C是由类A派生而来,它里面有一个纯虚函数,由于有虚函数的原因,有一个指向虚函数的指针(vptr),在32位的系统分配给指针的大小为4个字节,所以最后得到C类的大小为4.
类D是由类B,C派生迩来的,它的大小应该为二者之和5,为什么却是8呢?这是因为为了提高实例在内存中的存取效率.类的大小往往被调整到系统的整数倍.采取对长补齐,采用就近最近的整数倍数,就是该类的大小,所以类D的大小为8个字节.
当然在不同的编译器上上述得到的结果可能会不同。
例二:
#include<iostream>
using namespace std;
class A{
private:
int data;
};
class B{
private:
int data;
static int data1;
};
int B::data1=0;
void main()
{
cout<<"sizeof(A) = "<< sizeof(A) <<endl;
cout<<"sizeof(B) = "<< sizeof(B) <<endl;
}
输出结果:
sizeof(A) = 4
sizeof(B) = 4
请按任意键继续. . .
为什么类B多了一个数据成员,却大小和类A的大小相同呢?
因为:
类B的静态数据成员被编译器放在程序的一个global data segment中,它是类的一个数据成员.但是它不影响类的大小,不管这个类实际产生了多少实例,还是派生了多少新的类,静态成员数据在类中永远只有一个实体存在,而类的非静态数据成员只有被实例化的时候,他们才存在.但是类的静态数据成员一旦被声明,无论类是否被实例化,它都已存在.可以这么说,类的静态数据成员是一种特殊的全局变量.
所以A,B的大小相同.
例三:
现在来看一个有构造函数,和析构函数的类的大小!
#include<iostream>
using namespace std;
class A{
public :
A(int a)
{
a=x;
}
void f(int x)
{
cout<<x<<endl;
}
~A(){}
private:
int x;
int g;
};
class B{
public:
private:
int data;
int data2;
static int xs;
};
int B::xs=0;
void main()
{
A s(10);
s.f(10);
cout << "sizeof(a): "<< sizeof(A) << endl;
cout << "sizeof(b): "<< sizeof(B) << endl;
}
输出结果:
10
sizeof(a): 8
sizeof(b): 8
请按任意键继续. . .
它们的结果均相同,可以看出类的大小与它当中的构造函数,析构函数,以及其他的成员函数无关,只与它当中的成员数据有关.
例四:
#include<iostream>
using namespace std;
class X {};
class Y : public virtual X {};
class Z : public virtual X {};
class A : public Y, public Z {};
// 上述为经典的钻石继承
int main()
{
cout << "sizeof(X): " << sizeof(X) << endl;
cout << "sizeof(Y): " << sizeof(Y) << endl;
cout << "sizeof(Z): " << sizeof(Z) << endl;
cout << "sizeof(A): " << sizeof(A) << endl;
return 0;
}
输出结果:
sizeof(X): 1
sizeof(Y): 4
sizeof(Z): 4
sizeof(A): 8
请按任意键继续. . .
如`例一`一样,类X是一个空的类,但它事实上并不是空的,它有一个隐晦的1 type,那是被编译器安插进去的一个char,它主要使这个类的对象可以在内存中配置一个独一无二的地址。
如:
X a, b;
if (&a == &b)
{
cerr << "oh, my god!" << endl;
}
类Y,类Z的大小受三个因素的影响:
1)语言本身所造成的额外负担:因为类Y与类Z都是公有虚继承于类X,这里将有vptr,在32位上为4个字节。
2)编译器对特殊情况所做的优化处理:这里做了优化,一个空的虚基类被放在了派生类的最开头的部分,也就是说它未花费任何额外的空间,这就节省了类X的1字节,
如未做优化:一个空的虚基类被放在了派生类的固定不变动的尾端。
3)Alignment 对长补齐的限制:对长补齐,上述未做优化,则虚基类X的1个字节与类Y的4个字节(共5个字节),补齐3个字节,最终为8个字节。
做优化,则虚基类X的1个字节将被省略,只有类Y的4个字节(共4个字节),不需要补齐,最终为4个字节。
注:补齐就是将数值调整为某个整数的整数倍,在32位机器上,一般Alignment 为 4 个字节。
记住:一个虚基类对象只会在派生类中存在一份实体,不管它在继承体系中出现了多少次!
类A的大小:
1)类X,所有继承类唯一共享的实体,大小为1 type.
2)类Y:做了优化,只有4 type
3)类Z:同上,只有4 type
4)类A:1 + 4 + 4 = 9 type,补齐为12 type,做了优化,4+4=8
从以上的几个例子总结类的大小:
1).为类的非静态成员数据的类型大小之和.
2).有编译器额外加入的成员变量的大小,用来支持语言的某些特性(如:指向虚函数的指针).
3).为了优化存取效率,进行的边缘调整.
4).与类中的构造函数,析构函数以及其他的成员函数无关.