void print_struct_sizeof()
{
typedef struct A_{
float a ;
char b;
int c;
double d;
int *pa;
char *pc;
short e;
}A;
cout << "The size of the struct is: " << sizeof(A)<< endl;
}
C声明 32bit机器 64bit机器
char 1 1
short int 2 2
int 4 4
unsigned int 44
long int/long 48
float 4 4
double 8 8
char * 4 4 (特别需要注意这个)
如果用sizeof关键字来求这些值的话,一般会得到上面的这些值,但是结果还和编译器有关,所以如果碰到结果不是上面的值也不要奇怪。
2. 下面我们再回到刚才讲的题目中来,如果你在VS中运行,发现上面你的结果是40,但是为什么是40呢?这里先给出一个简单的,直观的解释,其实这种解释并不是本质上的原因,只是为了方便解决问题,其实本质的原因是 编译器处理时的内存对齐机制, 我们后面会进一步谈到。
编译器在为结构体分配内存时,是按照这个结构体中占用内存字节数最大的那个变量的字节数来依次分配的,然后在这块内存中按照结构体变量中的声明顺序来依次分配空间,如果发现当前的空间不足以分配给新的变量,那么会申请新的依旧是这个数量级的内存来进行分配,而之前的会被默认的填充。我们先不解释编译器为什么要这么来做,根据上面的分析,先来看一下刚才那个题目是怎么分配内存的:
double d;是所有这些里面占有内存字节数最大的,是8;所以编译器先分配一个8B的空间。
a占用(4B) + b占用(1B) 还剩3B不够分配给c,所以剩下的3B不做分配,空闲(3B),
再申请8B,c占用(4) + 空闲(4)
再申请8B,d占用(8)
在申请8B, pa(4) + pc(4)
在申请8B, e(2) + 空闲(6)
所以我们可以看到,总共申请了5次, 8B的空间。
为了加深理解,我们将刚才函数中结构体声明变量的顺序换了一下,如下所示:
void print_struct_sizeof()
{
struct A_{
int c;
float b;
char a;
short e;
double d;
int *pa;
char *pc;
}A;
cout << "The size of the struct is: " << sizeof(A)<< endl;
}
此时,输出的是32,大家可以自己计算一下。
3. 下面我们继续深入的学习,编译器为什么要这么做,以及究竟是怎么做的。
我们在其他资料中查到,编译器为了提高CPU的存储速度,对结构体中一些变量的起始地址做了"对齐”处理。在默认情况下,规定各成员变量存放的起始地址相对于结构的起始地址的偏移量必须为该变量的类型所占用的字节数的倍数。如,int型的变量,需要它距离起始地址空间的偏移量为sizeof(int)即4的倍数。而且就是因为这种对齐方式才有了我们上面的分析。而且默认的分配方式要求最后分配的结构体空间的总大小需要是结构体变量中占用最大字节数的整数倍。
下面我们来看一个例子加深理解:
struct MyStruct
{
char dda;//偏移量为0,满足对齐方式,dda占用1个字节;
double dda1;//下一个可用的地址的偏移量为1,不是sizeof(double)=8的倍数,需要补足7个字节才能使偏移量变为8,因此VC自动填充7个字节,dda1存放在偏移量为8
int type;//下一个可用的地址的偏移量为16,是sizeof(int)=4的倍 数,满足int的对齐方式,type存 放在偏移量为16的地址上,它占用4个字节。
};
所有成员变量都分配了空间,空间总的大小为1+7+8+4=20,但总大小不是结构的节边界数(即结构中占用最大空间的类型所占用的字节数sizeof(double)=8)的倍数,所以需要填充4个字节,以满足结构的大小为sizeof(double)=8的倍数。
当然,编译器也提供给我们自己的方法来进行字节的对齐,通过使用#pragma pack(n)来设定变量以n字节对齐方式。如果以n字节为对齐方式会有出现两种情况:
第一、如果n大于等于该变量所占用的字节数,那么偏移量就使用默认的对齐方式即sizeof()的整数倍。
第二、如果n小于该变量的类型所占用的字节数,那么偏移量为n的倍数,不用满足默认的对齐方式。
结构的总大小也有个约束条件,分下面两种情况:如果n大于所有成员变量类型所占用的字节数,那么结构的总大小必须为占用空间最大的变量占用的空间数的倍数; 否则必须为n的倍数。
下面举例说明其用法。
#pragma pack(push) //保存对齐状态
#pragma pack(push) //保存对齐状态
#pragma pack(4)//设定为4字节对齐
struct test
{
char m1;
double m4;
int m3;
};
#pragma pack(pop)//恢复对齐状态
以上结构的大小为16,下面分析其存储情况,首先为m1分配空间,其偏移量为0,满足我们自己设定的对齐方式(4字节对齐),m1占用1个字节。接着开始为m4分配空间,这时其偏移量为1,需要补足3个字节,这样使偏移量满足为n=4的倍数(因为sizeof(double)大于n),m4占用8个字节。接着为m3分配空间,这时其偏移量为12,满足为4的倍数,m3占用4个字节。这时已经为所有成员变量分配了空间,共分配了16个字节,满足为n的倍数。如果把上面的#pragma pack(4)改为#pragma pack(16),那么我们可以得到结构的大小为24。(请读者自己分析)