什么是内存对齐
现代计算机中内存空间都是按照byte划分的,从理论上讲似乎对任何类型的变量的访问可以从任何地址开始,但实际情况是在访问特定变量的时候经常在特定的内存地址访问,这就需要各类型数据按照一定的规则在空间上排列,而不是顺序的一个接一个的排放,这就是对齐。
为什么要内存对齐
平台原因:
某些硬件平台只能在某些地址处取某些特定类型的数据,否则抛出硬件异常。
性能原因:
为了访问未对齐的内存,处理器需要作两次内存访问;而对齐的内存访问仅需要一次访问。
总的来说,内存对齐就是拿空间换取时间的做法,目的是为了让CPU能一次获取到数据,从而提升性能。
#pragma pack()
该预处理指令用来改变对齐参数。在缺省情况下,C编译器为每一个变量或数据单元按其自然对界条件分配空间。一般地,可以通过下面的方法来改变缺省的对齐参数:
· 使用伪指令#pragma pack (n),C编译器将按照n字节对齐。
· 使用伪指令#pragma pack (),取消自定义字节对齐方式。
也可以写成:
#pragma pack(push,n)
#pragma pack(pop)
#pragma pack (n)表示每个成员的对齐单元不大于n(n为2的整数次幂)。这里规定的是上界,只影响对齐单元大于n的成员,对于对齐字节不大于n的成员没有影响。
内存对齐规则
每个特定平台上的编译器都有自己的默认“对齐系数”(也叫对齐模数)。程序员可以通过预编译命令#pragma pack(n),n=1,2,4,8,16来改变这一系数,其中的n就是你要指定的“对齐系数”。
规则:
1、数据成员对齐规则:结构(struct)(或联合(union))的数据成员,第一个数据成员放在offset为0的地方,以后每个数据成员的对齐按照#pragma pack指定的数值和这个数据成员自身长度中,比较小的那个进行。
2、结构(或联合)的整体对齐规则:在数据成员完成各自对齐之后,结构(或联合)本身也要进行对齐,对齐将按照#pragma pack指定的数值和结构(或联合)最大数据成员长度中,比较小的那个进行。
3、结合1、2颗推断:当#pragma pack的n值等于或超过所有数据成员长度的时候,这个n值的大小将不产生任何效果。
内存对齐实例
#include <stdio.h>
#include <stdint.h>
#pragma pack(1)
typedef struct {
/*成员对齐*/
int a; //长度4 < 1 **按1对齐**;偏移量为0;存放位置区间[0,3]
char b; //长度1 = 1 **按1对齐**;偏移量为4;存放位置区间[4]
short c; //长度2 > 1 **按1对齐**;偏移量为5;存放位置区间[5,6]
char d; //长度1 = 1 **按1对齐**;偏移量为6;存放位置区间[7]
/*整体对齐*/
//整体对齐系数 = min(对齐系数1,最大成员长度4) = 1,无需对齐,整体大小为8;
}test_pack1;
#pragma pack()
#pragma pack(2)
typedef struct {
/*成员对齐*/
int a; //长度4 > 2 **按2对齐**;偏移量为0;存放位置区间[0,3]
char b; //长度1 < 2 **按1对齐**;偏移量为4;存放位置区间[4]
short c; //长度2 = 2 **按2对齐**;偏移量要提升到2的倍数6;存放位置区间[6,7]
char d; //长度1 < 2 **按1对齐**;偏移量为7;存放位置区间[8];共九个字节
/*整体对齐*/
//整体对齐系数 = min(对齐系数2,最大成员长度4) = 2,将9提升到2的倍数10,整体大小为10;
}test_pack2;
#pragma pack()
#pragma pack(4)
typedef struct {
/*成员对齐*/
int a; //长度4 = 4 **按4对齐**;偏移量为0;存放位置区间[0,3]
char b; //长度1 < 4 **按1对齐**;偏移量为4;存放位置区间[4]
short c; //长度2 < 4 **按2对齐**;偏移量要提升到2的倍数6;存放位置区间[6,7]
char d; //长度1 < 4 **按1对齐**;偏移量为7;存放位置区间[8];总大小为9
/*整体对齐*/
//整体对齐系数 = min(对齐系数4,最大成员长度4) = 4,将9提升到4的倍数12,整体大小为12;
}test_pack4;
#pragma pack()
#pragma pack(8)
typedef struct {
/*成员对齐*/
int a; //长度4 < 8 **按4对齐**;偏移量为0;存放位置区间[0,3]
char b; //长度1 < 8 **按1对齐**;偏移量为4;存放位置区间[4]
short c; //长度2 < 8 **按2对齐**;偏移量要提升到2的倍数6;存放位置区间[6,7]
char d; //长度1 < 8 **按1对齐**;偏移量为7;存放位置区间[8],总大小为9
/*整体对齐*/
//整体对齐系数 = min(对齐系数8,最大成员长度4) = 4,将9提升到4的倍数12,整体大小为12;
}test_pack8;
#pragma pack()
test_pack1 pack1 = {0x11111111, 0x22, 0x3333, 0x44};
uint8_t *ptrPack1 = (uint8_t *)&pack1;
test_pack2 pack2 = {0x11111111, 0x22, 0x3333, 0x44};
uint8_t *ptrPack2 = (uint8_t *)&pack2;
test_pack4 pack4 = {0x11111111, 0x22, 0x3333, 0x44};
uint8_t *ptrPack4 = (uint8_t *)&pack4;
test_pack8 pack8 = {0x11111111, 0x22, 0x3333, 0x44};
uint8_t *ptrPack8 = (uint8_t *)&pack8;
int main(int argc, char *argv[])
{
int i = 0;
printf("#pragma pack(1) \tsize:%2d,\t", sizeof(pack1));
for(i=0; i<sizeof(pack1); i++)
{
printf("%02x", ptrPack1[i]);
}
printf("\r\n");
printf("#pragma pack(2) \tsize:%2d,\t", sizeof(pack2));
for(i=0; i<sizeof(pack2); i++)
{
printf("%02x", ptrPack2[i]);
}
printf("\r\n");
printf("#pragma pack(4) \tsize:%2d,\t", sizeof(pack4));
for(i=0; i<sizeof(pack4); i++)
{
printf("%02x", ptrPack4[i]);
}
printf("\r\n");
printf("#pragma pack(8) \tsize:%d,\t", sizeof(pack8));
for(i=0; i<sizeof(pack8); i++)
{
printf("%02x", ptrPack8[i]);
}
printf("\r\n");
return 0;
}
该段测试实例测试同一结构体,为了观察方便,成员的每一个字节值按顺序分别为十六进制0xaa、0xbb、0xcc、0xdd。
分别进行1、2、4、8字节对齐方式对齐,计算其占用内存大小及在内存的排列的结果,具体对齐步骤见代码注释部分。
什么时候需要指定内存对齐
一般情况下都不需要对编译器进行的内存对齐规则进行修改,因为这样会降低程序的性能,只有这个结构在涉及到对外交互的情况下,比如这个结构需要对外协议交互、写入文件等。
原文出处:https://www.cnblogs.com/damaohai/p/11606950.html
来源:oschina
链接:https://my.oschina.net/u/4393418/blog/3251604