第一:空类的大小
{
};
运行cout<<"sizeof(CBase)="<<sizeof(CBase)<<endl;
sizeof(CBase)=1;
为什么空的什么都没有是1呢?
先了解一个概念:类的实例化,所谓类的实例化就是在内存中分配一块地址,每个实例在内存中都有独一无二的地址。同样空类也会被实例化(别拿豆包不当干粮,空类也是类啊),所以编译器会给空类隐含的添加一个字节,这样空类实例化之后就有了独一无二的地址了。所以空类的sizeof为1。
第二:一般非空类大小
{
int a;
char * p;
};
那么运行cout<<"sizeof(CBase)="<<sizeof(CBase)<<endl;之后输出什么?
运行结果:sizeof(CBase)=8
第三:有虚函数类
{
public :
CBase( void );
virtual ~ CBase( void );
private :
int a;
char * p;
};
再运行:sizeof(CBase)=12
“C++ 类中有虚函数的时候有一个指向虚函数的指针(vptr),在32位系统分配指针大小为4字节”。
第四步:有虚函数类的继承
基类就是上面的了不写了
public CBase
{
public :
CChild( void );
~ CChild( void );
private :
int b;
};
运行:cout<<"sizeof(CChild)="<<sizeof(CChild)<<endl;
输出:sizeof(CChild)=16;
可见子类的大小是本身成员变量的大小加上子类的大小。
###################################################
另外:
1. 空类
class A
{
};
void main()
{
printf("sizeof(A): %d\n", sizeof(A));
getchar();
}
得到结果为:1。
类的实例化就是给每个实例在内存中分配一块地址。空类被实例化时,会由编译器隐含的添加一个字节。所以空类的size为1。
2.虚函数
class A
{
virtual void FuncA();<br> virtual void FuncB();
};
得到结果:4
当C++ 类中有虚函数的时候,会有一个指向虚函数表的指针(vptr),在32位系统分配指针大小为4字节。所以size为4.
3.静态数据成员
class A
{
int a;
static int b;
virtual void FuncA();
};
得到结果:8
静态数据成员被编译器放在程序的一个global data members中,它是类的一个数据成员.但是它不影响类的大小,不管这个类实际产生了多少实例,还是派生了多少新的类,静态成员数据在类中永远只有一个实体存在。
而类的非静态数据成员只有被实例化的时候,他们才存在.但是类的静态数据成员一旦被声明,无论类是否被实例化,它都已存在.可以这么说,类的静态数据成员是一种特殊的全局变量.
所以该类的size为:int a型4字节加上虚函数表指针4字节,等于8字节。
4.普通成员函数
class A
{
void FuncA();
}
结果:1
类的大小与它的构造函数、析构函数和其他成员函数无关,只已它的数据成员有关。
5.普通继承
class A
{
int a;
};
class B
{
int b;
};
class C : public A, public B
{
int c;
};
结果为:sizeof(C) =12.
可见普通的继承,就是基类的大小,加上派生类自身成员的大小。
6.虚拟继承
class C : virtual public A, virtual public B
{
int c;
};
结果:16.
当存在虚拟继承时,派生类中会有一个指向虚基类表的指针。所以其大小应为普通继承的大小(12字节),再加上虚基类表的指针大小(4个字节),共16字节。
问题引入
定义一个结构体的一般形式为:
struct 结构体名
{
//类型说明符 成员名;
};
例如有如下结构体:
struct Stu
{
int id;
char sex;
float hight;
};
那么一个这样的结构体变量占多大内存呢?也就是
cout<<sizeof(Stu)<<endl; 会输出什么?
在了解字节对齐方式之前想当然的会以为:sizeof(Stu) = sizeof(int)+sizeof(char)+sizeof(float) = 9.
然而事实并非如此!
字节对齐原则
在系统默认的对齐方式下:每个成员相对于这个结构体变量地址的偏移量正好是该成员类型所占字节的整数倍,且最终占用字节数为成员类型中最大占用字节数的整数倍。
在这个例子中,id的偏移量为0(0=4*0),sex的偏移量为4(4=1*4),hight的偏移量为8(8=2*4),此时占用12字节,也同时满足12=3*4.所以sizeof(Stu)=12.
(1)出现继承关系时:
struct A{
int a;
char b;
};
struct B:A{
char c;
int d;
long long e;
};
基类的成员总是在派生类的前面。而且即使有字节对齐,基类对齐后派生类的成员不会占用基类填充的字节,即计算好基类所占字节数后,这些字节只能由基类拥有,不能被派生类的成员占用(即char b后面有3字节的填充,之后才有char c)在派生类中成员的分布只需满足每个变量起始字节序号为该类型所占字节数的整数倍且最终大小为占用字节数最大的类型对应的字节数的整数倍。排列如下:
/*
0 4 8 12 16 24
| | | | | |
aaaab---c---ddddeeeeeeee
*/
(2)出现关联关系时:
必须满足:1.结构体/类与结构体/类之间不会共用自动补齐的内存,即一个结构体变量/对象对齐之后填补的内存不允许被其他变量/对象占用;2.结构体的起始字节位置必须是该结构体中所占字节数最大的变量的字节数的整数倍;3.最终所占字节数必须是最大所占字节数最大的变量的字节数的整数倍。
(3)出现强制对齐方式时:
当然,有时候考虑到其他特殊用途,使用#pragma pack(n)来设定以n字节对齐的方式(n可取2的较小次幂,即1,2,4,8,具体取值范围以及默认值与所使用的编译器有关。笔者所测试的环境下vs/vc默认为8,gcc默认为4)。
n字节对齐就是说变量存放的起始地址的偏移量有两种情况:
第一、如果n大于等于该变量所占用的字节数,那么偏移量必须满足默认的对齐方式;
第二、如果n小于该变量的类型所占用的字节数,那么偏移量为n的倍数,不用满足默认的对齐方式。
结构的总大小也有个约束条件,分下面两种情况:如果n大于所有成员变量类型所占用的字节数,那么结构的总大小必须为占用空间最大的变量占用的空间数的倍数;否则必须为n的倍数。
比如有以下代码:
1 #pragma pack(push) //保存对齐状态
2 #pragma pack(4)//设定为4字节对齐
3 struct Test
4 {
5 char c1;
6 double d;
7 int i;
8 char c2;
9 };
10 #pragma pack(pop)//恢复对齐状态
这时候编译器就会被迫使用我们约定的字节对齐方式,即4字节对齐,因此c1占4字节,d占8字节,i占4字节,c2占4字节,共20字节;
如果我们没有设置字节对齐方式,仍然使用默认对齐的话,这里sizeof(Test) = 24。
注:以上测试效果及完整代码见github.
最后一个问题,为什么要进行字节对齐?
《Windows核心编程》里这样说:当CPU访问正确对齐的数据时,它的运行效率最高,当数据大小的数据模数的内存地址是0时,数据是对齐的。例如:WORD值应该是总是从被2除尽的地址开始,而DWORD值应该总是从被4除尽的地址开始,数据对齐不是内存结构的一部分,而是CPU结构的一部分。当CPU试图读取的数值没有正确的对齐时,CPU可以执行两种操作之一:产生一个异常条件;执行多次对齐的内存访问,以便读取完整的未对齐数据,若多次执行内存访问,应用程序的运行速度就会慢。
补充:C++类中的数据成员也遵循以上对齐原则,也就是说:
1.在不考虑(或者说在没有)虚函数和虚继承的情况下,sizeof(自定义类)也按照类似上面的方式来计算。
2.如果一个类拥有虚函数或者虚继承,则在数据成员的基础上相当于多一个指针类型的数据成员(位置在所有数据成员的前面),最后计算时加上即可。
3.如果一个类或者结构体不含有任何数据成员,且无虚函数以及虚继承,则sizeof()结果为1。
4.静态成员不在计算范围。