内存对齐

什么是内存对齐

现代计算机中内存空间都是按照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字节对齐方式对齐,计算其占用内存大小及在内存的排列的结果,具体对齐步骤见代码注释部分。

  

什么时候需要指定内存对齐

一般情况下都不需要对编译器进行的内存对齐规则进行修改,因为这样会降低程序的性能,只有这个结构在涉及到对外交互的情况下,比如这个结构需要对外协议交互、写入文件等。

 

posted @ 2019-09-29 11:44  宇哥来了  阅读(1778)  评论(0编辑  收藏  举报