为什么有内存对齐
1. 平台原因(移植原因):
不是所有的硬件平台都能访问任意地址上的任意数据的;某些硬件平台只能在某些地址处取某些特定类型的数据,否则会抱出硬件异常。
因为:(基于这个原因,就要把某些数据对齐到能够取得、访问、修改、的地址处,这样才能去获得对应数据)
2. 性能原因:
数据结构(尤其是栈)应该尽可能地在自然边界上对齐。
原因在于,为了访问未对齐的内存,处理器需要作两次内存访问;而对齐的内存访问仅需要一次访
问。
什么意思?
以结构体S为例
struct S
{char c;int i;
};
如果没有对齐,char c;存放了之后,int i; 直接存放在char c后面,可能有时候对齐时刚好会出现这种情况,但不是每一次都这样。我们直到计算机有32位、64位,也就是每次读取的数据4个字节、8个字节。以4个字节为例,如果没有对齐,读取 c 时会将 i 的也读取一部分出来,导致出错。如果读取 i ,因为没有对齐,数据从0号开始读,i数据没读完,还要再读一次,浪费时间。在对齐的内存空间上就不会出现这种情况。读取 i 时可以从4号位开始读,这样就可以一次性读取完 i 的数据,不用担心数据读取不完全或者错误。
但是总体来说:
结构体的内存对齐是拿空间来换取时间的做法。
那在设计结构体的时候,我们既要满足对齐,又要节省空间,如何做到:
让占用空间小的成员尽量集中在一起。
规则
1. 第一个成员在与结构体变量偏移量为0的地址处。
2. 其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处。
对齐数 = 编译器默认的一个对齐数 与 该成员大小的较小值。(VS中默认的值为8)
3. 结构体总大小为最大对齐数(每个成员变量都有一个对齐数)的整数倍。
4. 如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整
体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍。
gcc,Linux中没有默认对齐数,对齐数就是成员自身的大小,默认对齐数可以修改 #pragma pack()
括号填入4、8即可,填入1就认为没有对齐数。
规则解释
假设现在有结构体S1,现在要求该结构体的内存大小
struct S1
{char c1;int i;char c2;
};printf("%d\n",seziof(struct S1));//结果是12
规则1解释:
很容易理解,就是不管什么类型,只要是结构体的第一个成员就要从0号偏移地址开始往下存储。
所以char c1;从0偏移处开始存放,自己是多大的字节就占多少个内存地址
规则2解释:
我使用的是VS编译器,对默认齐数是8(gcc,linux系统没有对齐数,其实也无所谓,反正对齐数就是该类型的字节大小)。这里我们的第二个成员是 int 型的,占4个字节大小,如果考虑默认的对齐数则:int i;的字节大小是4,VS默认是8,这两者的较小值就是4,所以4就是对齐数。
则 i 存放在偏移量是4的倍数的地址上,比如4,8,12...
所以 i 从4号地址开始往下存放,i 是int型,所以从4到7都是i占据的内存
规则3解释:
在c1,i,c2中,对齐数分别是1,4,1
则在这个结构体里面最大的对齐数就是4
在存放过程中,c1,i,c2已经存放完毕,占用0-8一共9个字节地址
但是9不是最大对齐数4的倍数,所以还要往下浪费一些空间直到该地址偏移量是4的倍数
所以再往下浪费三个地址到11号地址处(这里的号数只是为了方便理解取的)
在做题过程中要记住的是偏移地址的个数,而不是自己设定的号数
验证方法
为了验证结论,可以使用offsetof函数打印存放的号数是不是和我们的一样。
这个函数的头文件是 #include <stddef.h>
函数原型:
size_t offsetof( structName, memberName );
第一个参数是结构体的名字,第二个参数是结构体成员的名字。该宏返回结构体structName中成员memberName的偏移量。偏移量是size_t类型的。
printf("%d\n",offsetof(struct S1,c1));
printf("%d\n",offsetof(struct S1,i));
printf("%d\n",offsetof(struct S1,c2));
规则4解释:
计算S4的内存大小
struct S3
{double d;char c;int i;
};struct S4
{char c1;struct S3 s3;double d;
};
先计算S3的内存大小是16;
在S4里的S3的最大对齐数是8(double d;),所以S3存放的对齐数要是8的整数倍;从8号位开始存放结构体S3,一共占据16个字节大小。第三个成员double d;的字节大小是8,对齐数也是8,下面是8的倍数的近距离刚好是24,所以从24号开始存放到31号刚刚好8个字节。
此时占了32字节,在这两个结构体中,所有对齐数里面最大的就是8,32是8的倍数,所以不需要往下浪费地址
切记
对齐数的倍数从0号地址开始,用偏移号数来找对齐的倍数。
什么意思:比如结构体的第一个char a;肯定从0号位开始存,而且char只占用一个字节地址;
第二个是double b;本身字节大小是8,对齐数也是8,存放时就要是8的倍数,此时应该从0号开始数8个地址,0-7刚好是8个位,8-15地址号存放double b。
如图: