32位系统中,cpu按块大小读取内存,每个块的大小为4字节(64位系统是8字节)。而在C语言中自定义的数据类型(例如结构体)占用的内存大小可能不满足4或8的整数倍,则会出现同一变量需要两次读取才能读完的情况,所以往往采用内存对齐来提高访问效率(以空间换时间)。
内存对齐规则
对于结构体类型数据:
1.结构体内的第一个变量的地址偏移量为0
2.结构体内的第二个变量的起始地址要为该变量类型大小的整数倍与对齐模数比中的最小值
(系统默认的对齐模数比可使用#pragma pack(show)语句查看,也可以使用#pragma pack(n)进行设置,其中n为2的任意次幂例如1,2,4,8...)
3.结构体所有变量对齐结束后,再对结构体整体进行对齐。整体占用的内存大小要为结构体中最大的变量类型与对齐模数比中的较小值的整数倍。
4.若存在结构体嵌套,则将子结构体中的最大的数据类型作为子结构体的内存对齐标准
实例
#include
#pragma pack(4) //设置对齐模数比为4
typedef struct {
char a; //内存0(由于第二个变量从4开始,则实际上1~3的空间被系统自动充填了)
int b; //对齐模数比为4,int类型也为4,则a的内存地址为4~7
double c; //内存地址8~15
float d; //内存地址16~19
}test_object; //test_object的总内存大小为20字节是对齐模数比4的倍数,则不用二次对齐
int main(){
printf("size of test_object:%d",sizeof(test_object));
return 0;
}
输出:
size of test_object:20
同样的结构体,设置不同的对齐模数比则会得到不同的结果:
#include
#pragma pack(8) //设置对齐模数比为8
typedef struct {
char a; //内存0(由于第二个变量从4开始,则实际上1~3的空间被系统自动充填了)
int b; //对齐模数比为8,int类型为4,取较小值,则a的内存地址为4~7
double c; //内存地址8~15
float d; //对齐模数比为8,float类型为4,取较小值,内存地址16~19
}test_object; //test_object的总内存大小为20字节不是对齐模数比8的倍数,则需要二次对齐,末尾补0,把20➡24
int main(){
printf("size of test_object:%d",sizeof(test_object));
return 0;
}
输出:
size of test_object:24
测试结构体嵌套的情况:
#include
#pragma pack(8) //设置对齐模数比为8
typedef struct {
char a; //内存地址0(由于第二个变量从4开始,则实际上1~3的空间被系统自动充填了)
int b; //对齐模数比为8,int类型为4,取较小值,则a的内存地址为4~7
double c; //内存地址8~15
float d; //对齐模数比为8,float类型为4,取较小值,内存地址16~19
}test_object; //test_object的总内存大小为20字节不是对齐模数比8的倍数,则需要二次对齐,末尾补0,把20➡24
typedef struct{
char e; //内存地址0~7
test_object t; //结构体中最大数据类型为double(8字节),对齐模数比为8,则内存地址为8~31
float f; //对齐模数比为8,float类型为4,取较小值,则内存地址32~35
}test_object2; //test_object2的总内存大小为36不是对齐模数比8的倍数,需要二次对齐,末尾补0,把36→40
int main(){
printf("size of test_object:%d\n",sizeof(test_object));
printf("size of test_object:%d",sizeof(test_object2));
return 0;
}
输出:
size of test_object:24
size of test_object2:40
计算公式
公式一:
( (sizeof (TYPE) + align -1) / align ) * align
// 这里 除以align 又 乘以align貌似什么也没做,但事实上C语言中"/"会自动取整
//例如在C语言中, (23+4-1) / 4 = 6, 实际上这个算式的运算结果应该是6.5,
但是由于"/"的自动取整,小数部分直接会被舍弃,所以C语言中 ((23 + 4 -1 )/4) * 4 =24
24是4的倍数,从而实现了对于TYPE类型的数据进行4字节对齐的过程。
公式二:
((sizeof(TYPE)+ align -1) & ~(align -1))
公式三:
((8*sizeof(TYPE)+ 8*align -1)>>k)<>5)<<2; //设置像素点4字节(32位)对齐
其中TYPE表示要进行内存对齐的数据类型,align表示要设置的字节对齐模数
单看这几个公式可能比较难以理解,下面举一些例子进行说明:
例如,要进行4字节对齐即该数据类型占用的字节数应该为4的倍数(即该数据类型大小的末尾应该是00)
8字节对齐(末尾为000)
16字节对齐(末尾为0000)
......
要如何实现上述的整除要求呢?下面以公式二为例进行讲解。
公式二中:
sizeof(TYPE) 表示TYPE类型所占用的字节数,假设为23 Bytes
align 表示要设置的字节对齐模数,假设为 4 Bytes
则 align -1 = 3 (二进制是11)
sizeof(TYPE)+ align -1 = 23+ 3 =26 (二进制是11010)
~(align -1) = (二进制是00)
((sizeof(TYPE)+align -1)&~(align -1)) = 24 (二进制是11000)
二进制的加法过程如下:
10111
+ 11
------------
11010
& 00
------------
11000
总结:
公式二其实就是通过 " +(align-1) " 在末尾引入对应数量的1,再通过 " & ~(align-1)" 把这些引入的1都变成0,即实现了末尾出现合适数量的0以达到可以被某个2的倍数整除的效果。公式一和公式三也是做了类似的事情,只不过对于计算机来说运行的效率有所不同而已。
参考:
C语言提高深入浅出 53 内存对齐-知识-名师课堂-爱奇艺
内存对齐算法_godleading的专栏-CSDN博客_内存对齐算法
C语言内存对齐详解 - wuyudong - 博客园