问题
突然收到了一个问题:
#include<stdio.h>
#include <math.h>
struct icd
{
int a; //4
char b; //1
double c; //8
};
struct cdi
{
char a;
double b;
int c;
};
int main(int argc, char const *argv[])
{
printf("%d
", sizeof(struct icd));
printf("%d
", sizeof(struct cdi));
}
这段代码的输出结果为:
16
24
理论上输出的结果应该是:13
好像有什么不对。
解答
查阅资料,这是编译器优化的问题。
内存对齐
现代计算机中内存空间都是按照 byte 划分的从理论上讲似乎对任何类型的变量的访问可以从任何地址开始,但实际情况是在访问特定类型变量的时候经常在特定的内存地址访问,这就需要各种类型数据按照一定的规则在空间上排列,而不是顺序的一个接一个的排放,这就是对齐。
为什么要内存对齐
字节对齐主要是为了提高内存的访问效率,比如intel 32为cpu,每个总线周期都是从偶地址开始读取32位的内存数据,如果数据存放地址不是从偶数开始,则可能出现需要两个总线周期才能读取到想要的数据,因此需要在内存中存放数据时进行对齐。
比如,有些平台每次读都是从偶地址开始,如果一个int型(假设为32位系统)如果存放在偶地址开始的地方,那么一个读周期就可以读出这32bit,而如果存放在奇地址开始的地方,就需要2个读周期,并对两次读出的结果的高低字节进行拼凑才能得到该32bit数据。
有效对齐值(新概念)
是 #pragma pack指定值 和 结构体中最长数据类型长度 中较小的那个。有效对齐值也叫对齐系数。
每个特定平台上的编译器都有自己的默认“对齐系数”。可以通过预编译命令#pragma pack(n)
n一般取1,2,3,4,8,16
GCC编译器默认为8
对齐规则
-
结构体变量的首地址是有效对齐值(对齐单位)的整数倍。
-
结构体第一个成员的偏移量(offset)为0,以后每个成员相对于结构体首地址的 offset 都是该成员大小与有效对齐值中较小那个的整数倍,如有需要编译器会在成员之间加上填充字节。
-
结构体的总大小为 有效对齐值 的整数倍,如有需要编译器会在最末一个成员之后加上填充字节。
-
结构体内类型相同的连续元素将在连续的空间内,和数组一样。
分析代码
struct icd
{
int a; //4
char b; //1
double c; //8
};
第一个成员为int型,所以按照4个字节对齐(第一个成员的偏移值都是0)字节对齐,说白了就是当前的偏移地址必须为n的整数倍,比如4个字节对齐,就是说当前的偏移字节数必须为4的倍数,因为第一个成员都是从offset为0开始的,所以先上4个字节来存int
第二个为char,占1个字节,前面有一个int,4个字节,此时有效对齐值为4,所以必须在填3个字节的空白字节。
最后一个为double类型的,占8个字节,此时有效对齐值为8,直接存入double
4+4+8=16
struct cdi
{
char a;
double b;
int c;
};
第一个成员为char型,所以按照1个字节对齐
然后第二个为double,占8个字节,也就是此时偏移地址,必须为8的倍数,明显前面只有1个字节,不能被8整除,所以必须在填7个字节的空白字节
最后一个为int类型的,占4个字节。
8+8+4=20
因为20不是有效对齐值的整数倍,所以在最末成员后填充4个字节。
20+4=24