深度剖析结构体(struct)的内存对齐方式
首先,我们来看一个例子,请计算 struct Test1的内存大小为多少?
struct Test1
{
char c1;
char c2;
short s;
int i;
};
这个题目比较简单,我们知道结构体的本质为一组变量的集合,所以sizeof(struct Test1)=sizeof(c1)+sizeof(c2)+sizeof(s)+sizeof(i)=1+1+2+4=8,所以上述例子的结构体大小为8字节。
接下来再来看一个例子,把struct Test1中的成员变量稍微变一下位置为struct Test2
struct Test2
{
char c1;
short s;
char c2;
int i;
};
这时候sizeof(struct Test1)=sizeof(c1)+sizeof(c2)+sizeof(s)+sizeof(i)=1+1+2+4=8 吗??当然 不是,这时候sizeof(struct Test2)=12,为什么是这个结果呢?
这个就是内存对齐产生的结果,当时C语言诞生是为了高效的目的,所以CPU对内存的读取是不连续的,是按照字节读取的,按照 1、2、4、8、16...字节读取。当然CPU为什么要按照这样的读取呢?1.当读取操作的数据没有对齐时候,则需要两次总线周期来访问内存,因此性能就会大大折扣,所以CPU读取内存就是不连续的,就是按照 1、2、4、8、16...字节读取。2.某些在嵌入式硬件平台只能从规定的相对地址处读取特定的类型数据,否则产生硬件异常。需要说明的是CPU读取内存是按照默认4字节读取的。C语言中就是#pragma pack(4)来 进行的。
struct 占用内存大小的具体计算步骤如下所示:
1.第一个成员的编译地址为0
2.每个成员按照其类型所占字节的大小和pack参数的大小进行比较,取其中较小的一个值为对齐参数
2.1其中偏移地址必须能够被对齐参数整除。
2.2 如果结构体中内嵌结构体变量,这时候相应的对齐参数为内嵌结构体变量数据成员最大的长度,作为对齐参数。
3.最后算出的结构体总长度必须为所有对齐参数的整数倍。
注意:在32位机器上,编译器默认为4字节对齐,即#pragma pack(4)
sizeof(Test1)=8 解释如下:
#pragma pack(4)
struct Test1
{ //对齐参数 偏移地址 内存大小
char c1; 1 0 1
char c2; 1 1 1
short s; 2 2 2
int i; 4 4 4
};
#pragma pack()
所以 sizeof(struct Test1)=4+4=8
有了上述理论的基础,我们在来看看sizeof(struct Test2)=12这是为什么。解释如下:
#pragma pack(4)
struct Test2
{ //对齐参数 偏移地址 内存大小
char c1; 1 0 1
short s; 2 2 2
char c2; 1 4 1
int i; 4 8 4
};
#pragma pack()
所以最后的结果:sizeof(struct Test2)=8+4=12
上述sizeof(struct Test1)有没有可能等于sizeof(struct Test2)呢??当然是有的,如果把对齐参数改为1,即#pragma pack(1),这时候二者就相等了。
如上图所示,通过#pragma pack(1) 改变编译器默认的对齐方式,使得sizeof(struct Test1)=sizeof(struct Test2)=8
再来看下面这道题目:
这道题规定8字节对齐,按照理论推导的结果(推导过程在代码上面)sizeof(struct S2)=24,但是最后gcc编译器得出的结果却是sizeof(struct Test2)=20,这是是为什么呢?因为我用的这款gcc编译器占时不支持8字节对齐,所以编译器根本不认识#pragma pack(8) 最后编译按照4字节来处理这句话,就变为#pragma pack(4)。所以最后sizeof(struct Test2)=20。
我们现在换一款编译器来看看,看看bcc32这款编译器,看看它的输出结果是怎么样的??
bcc32这款编译器就得出了我们最后想要的结果。因为bcc32这款编译器就支持8字节对齐。
最后,我们再换一款编译器来看看,我们用Qtcreator2.4来编译这段代码,看看。如下图所示:
结果和我们用bcc32编译器是一样的结果,说明最后答案是正确的,gcc编译器占时不支持8字节对齐。