您的位置:首页 > 编程语言 > C语言/C++

C++:C++ 空类的大小及相关继承类的大小

2015-02-05 16:03 176 查看
一个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

从以上的几个例子总结类的大小:

1).为类的非静态成员数据的类型大小之和.

2).有编译器额外加入的成员变量的大小,用来支持语言的某些特性(如:指向虚函数的指针).

3).为了优化存取效率,进行的边缘调整.

4).与类中的构造函数,析构函数以及其他的成员函数无关.
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: