C语⾔字节对齐⽐如MDK-KEIL字节对齐
__align(),__attribute((a。。。
C语⾔字节对齐
⽂章最后本⼈做了⼀幅图,⼀看就明⽩了,这个问题⽹上讲的不少,但是都没有把问题说透。
  ⼀、概念
  对齐跟数据在内存中的位置有关。如果⼀个变量的内存地址正好位于它长度的整数倍,他就被称做⾃然对齐。⽐如在32位cpu下,假设⼀个整型变量的地址为0x00000004,那它就是⾃然对齐的。
  ⼆、为什么要字节对齐
  需要字节对齐的根本原因在于CPU访问数据的效率问题。假设上⾯整型变量的地址不是⾃然对齐,⽐如为0x00000002,则CPU如果取它的值的话需要访问两次内存,第⼀次取从0x00000002-0x00000003的⼀个short,第⼆次取从0x00000004-0x00000005的⼀个short然后组合得到所要的数据,如果变量在0x00000003地址上的话则要访问三次内存,第⼀次为char,第⼆次为short,第三次为char,然后组合得到整型数据。⽽如果变量在⾃然对齐位置上,则只要⼀次就可以取出数据。⼀些系统对对齐要求⾮常严格,⽐如sparc 系统,如果取未对齐的数据会发⽣错误,举个例:
  char ch[8];
  char *p = &ch[1];
  int i = *(int *)p;
  运⾏时会报segment error,⽽在x86上就不会出现错误,只是效率下降。
  三、正确处理字节对齐
  对于标准数据类型,它的地址只要是它的长度的整数倍就⾏了,⽽⾮标准数据类型按下⾯的原则对齐:
  数组 :按照基本数据类型对齐,第⼀个对齐了后⾯的⾃然也就对齐了。
  联合 :按其包含的长度最⼤的数据类型对齐。
  结构体: 结构体中每个数据类型都要对齐。
  ⽐如有如下⼀个结构体:
  struct stu{
  char sex;
  int length;
  char name[10];
  };
  struct stu my_stu;
  由于在x86下,GCC默认按4字节对齐,它会在sex后⾯跟name后⾯分别填充三个和两个字节使length和整个结构体对齐。于是我们sizeof(my_stu)会得到长度为20,⽽不是15.
  四、__attribute__选项
  我们可以按照⾃⼰设定的对齐⼤⼩来编译程序,GNU使⽤__attribute__选项来设置,⽐如我们想让刚才的结构按⼀字节对齐,我们可以这样定义结构体
  struct stu{
  struct stu{
  char sex;
  int length;
  char name[10];
  }__attribute__ ((aligned (1)));
  struct stu my_stu;
  则sizeof(my_stu)可以得到⼤⼩为15。
  上⾯的定义等同于
  struct stu{
  char sex;
  int length;
  char name[10];
  }__attribute__ ((packed));
  struct stu my_stu;
  __attribute__((packed))得变量或者结构体成员使⽤最⼩的对齐⽅式,即对变量是⼀字节对齐,对域(field)是位对齐.
  五、什么时候需要设置对齐
  在设计不同CPU下的通信协议时,或者编写硬件驱动程序时寄存器的结构这两个地⽅都需要按⼀字节对齐。即使看起来本来就⾃然对齐的也要使其对齐,以免不同的编译器⽣成的代码不⼀样.
⼀、快速理解
1. 什么是字节对齐?
在C语⾔中,结构是⼀种复合数据类型,其构成元素既可以是基本数据类型(如int、long、float等)的变量,也可以是⼀些复合数据类型(如数组、结构、联合等)的数据单元。在结构中,编译器为结构的每个成员按其⾃然边界(alignment)分配空间。各个成员按照它们被声明的顺序在内存中顺序存储,第⼀个成员的地址和整个结构的地址相同。
为了使CPU能够对变量进⾏快速的访问,变量的起始地址应该具有某些特性,即所谓的”对齐”. ⽐如4字节的int型,其起始地址应该位于4字节的边界上,即起始地址能够被4整除.
2. 字节对齐有什么作⽤?
字节对齐的作⽤不仅是便于cpu快速访问,同时合理的利⽤字节对齐可以有效地节省存储空间。
对于32位机来说,4字节对齐能够使cpu访问速度提⾼,⽐如说⼀个long类型的变量,如果跨越了4字节边界存储,那么cpu要读取两次,这样效率就低了。但是在32位机中使⽤1字节或者2字节对齐,反⽽会使变量访问速度降低。所以这要考虑处理器类型,另外还得考虑编译器的类型。在vc中默认是4字节对齐的,GNU gcc 也是默认4字节对齐。
3. 更改C编译器的缺省字节对齐⽅式
在缺省情况下,C编译器为每⼀个变量或是数据单元按其⾃然对界条件分配空间。⼀般地,可以通过下⾯的⽅法来改变缺省的对界条件:· 使⽤伪指令#pragma pack (n),C编译器将按照n个字节对齐。
· 使⽤伪指令#pragma pack (),取消⾃定义字节对齐⽅式。
另外,还有如下的⼀种⽅式:
· __attribute((aligned (n))),让所作⽤的结构成员对齐在n字节⾃然边界上。如果结构中有成员的长度⼤于n,则按照最⼤成员的长度来对齐。
· __attribute__ ((packed)),取消结构在编译过程中的优化对齐,按照实际占⽤字节数进⾏对齐。
4. 举例说明
例1
struct test
{
char x1;
short x2;
float x3;
char x4;
};
由于编译器默认情况下会对这个struct作⾃然边界(有⼈说“⾃然对界”我觉得边界更顺⼝)对齐,结构的第⼀个成员x1,其偏移地址为0,占据了第1个字节。第⼆个成员x2为short类型,其起始地址必须2字节对界,因此,编译器在x2和x1之间填充了⼀个空字节。结构的第三个成员x3和第四个成员x4恰好落在其⾃然边界地址上,在它们前⾯不需要额外的填充字节。在test结构中,成员x3要求4字节对界,是该结构所有成员中要求的最⼤边界单元,因⽽test结构的⾃然对界条件为4字节,编译器在成员x4后⾯填充了3个空字节。整个结构所占据空间为12字节。
例2
#pragma pack(1) //让编译器对这个结构作1字节对齐
struct test
{
char x1;
short x2;
float x3;
char x4;
};
#pragma pack() //取消1字节对齐,恢复为默认4字节对齐
这时候sizeof(struct test)的值为8。
例3
#define GNUC_PACKED __attribute__((packed))
struct PACKED test
{
char x1;
short x2;
float x3;
char x4;
}GNUC_PACKED;
这时候sizeof(struct test)的值仍为8。
⼆、深⼊理解
什么是字节对齐,为什么要对齐?
TragicJun 发表于 2006-9-18 9:41:00 现代计算机中内存空间都是按照byte划分的,从理论上讲似乎对任何类型的变量的访问可以从任何地址开始,但实际情况是在访问特定类型变量的时候经常在特定的内存地址访问,这就需要各种类型数据按照⼀定的规则在空间上排列,⽽不是顺序的⼀个接⼀个的排放,这就是对齐。
对齐的作⽤和原因:各个硬件平台对存储空间的处理上有很⼤的不同。⼀些平台对某些特定类型的数据只能从某些特定地址开始存取。⽐如有些架构的CPU在访问⼀个没有进⾏对齐的变量的时候会发⽣错误,那么在这种架构下编程必须保证字节对齐.其他平台可能没有这种情况,但是最常见的是如果不按照适合其平台要求对数据存放进⾏对齐,会在存取效率上带来损失。⽐如有些平台每次读都是从偶地址开始,如果⼀个int型(假设为32位系统)如果存放在偶地址开始的地⽅,那么⼀个读周期就可以
读出这32bit,⽽如果存放在奇地址开始的地⽅,就需要2个读周期,并对两次读出的结果的⾼低字节进⾏拼凑才能得到该32bit数据。显然在读取效率上下降很多。
⼆.字节对齐对程序的影响:
先让我们看⼏个例⼦吧(32bit,x86环境,gcc编译器):
设结构体如下定义:
struct A
{
int a;
char b;
short c;
};
struct B
{
char b;
int a;
short c;
};
现在已知32位机器上各种数据类型的长度如下:
char:1(有符号⽆符号同)
short:2(有符号⽆符号同)
int:4(有符号⽆符号同)
long:4(有符号⽆符号同)
float:4        double:8
那么上⾯两个结构⼤⼩如何呢?
结果是:
sizeof(strcut A)值为8
sizeof(struct B)的值却是12
gnu编译器
结构体A中包含了4字节长度的int⼀个,1字节长度的char⼀个和2字节长度的short型数据⼀个,B也⼀样;按理说A,B⼤⼩应该都是7字节。之所以出现上⾯的结果是因为编译器要对数据成员在空间上进⾏对齐。上⾯是按照编译器的默认设置进⾏对齐的结果,那么我们是不是可以改变编译器的这种默认对齐设置呢,当然可以.例如:
#pragma pack (2) /*指定按2字节对齐*/
struct C
{
char b;
int a;
short c;
};
#pragma pack () /*取消指定对齐,恢复缺省对齐*/
sizeof(struct C)值是8。
修改对齐值为1:
#pragma pack (1) /*指定按1字节对齐*/
struct D
{
char b;
int a;
short c;
};
#pragma pack () /*取消指定对齐,恢复缺省对齐*/
sizeof(struct D)值为7。
后⾯我们再讲解#pragma pack()的作⽤.
三.编译器是按照什么样的原则进⾏对齐的?
先让我们看四个重要的基本概念:
1.数据类型⾃⾝的对齐值:
对于char型数据,其⾃⾝对齐值为1,对于short型为2,对于int,float,double类型,其⾃⾝对齐值为4,单位字节。
2.结构体或者类的⾃⾝对齐值:其成员中⾃⾝对齐值最⼤的那个值。
3.指定对齐值:#pragma pack (value)时的指定对齐值value。
4.数据成员、结构体和类的有效对齐值:⾃⾝对齐值和指定对齐值中⼩的那个值。
有了这些值,我们就可以很⽅便的来讨论具体数据结构的成员和其⾃⾝的对齐⽅式。有效对齐值N是最终⽤来决定数据存放地址⽅式的值,最重要。有效对齐N,就是表⽰“对齐在N上”,也就是说该数据的"存放起始地址%N=0".⽽数据结构中的数据变量都是按定义的先后顺序来排放的。第⼀个数据变量的起始地址就是数据结构的起始地址。结构体的成员变量要对齐排放,结构体本⾝也要根据⾃⾝的有效对齐值圆整(就是结构体成员变量占⽤总长度需要是对结构体有效对齐值的整数倍,结合下⾯例⼦理解)。这样就不能理解上⾯的⼏个例⼦的值了。
例⼦分析:
分析例⼦B;
struct B
{
char b;
int a;
short c;
};
假设B从地址空间0x0000开始排放。该例⼦中没有定义指定对齐值,在笔者环境下,该值默认为4。第⼀个成员变量b的⾃⾝对齐值是1,⽐指定或者默认指定对齐值4⼩,所以其有效对齐值为1,所以其存放地址0x0000符合0x0000%1=0.第⼆个成员变量a,其⾃⾝对齐值为4,所以有效对齐值也为4,所以只能存放在起始地址为0x0004到0x0007这四个连续的字节空间中,复核0x0004%4=0,且紧靠第⼀个变量。第三个变量c,⾃⾝对齐值为2,所以有效对齐值也是2,可以存放在0x0008到0x0009这两个字节空间中,符合0x0008%2=0。所以从0x0000到0x0009存放的都是B内容。再看数据结构B的⾃⾝对齐值为其变量中最⼤对齐值(这⾥是b)所以就是4,所以结构体的有效对齐值也是4。根据结构体圆整的要求,0x0009到0x0000=10字节,(10+2)%4=0。所以0x0000A到0x000B也为结构体B所占⽤。故B从0x0000到0x000B共有12个字节,sizeof(struct B)=12;其实如果就这⼀个就来说它已将满⾜字节对齐了,因为它的起始地址是0,因此肯定是对齐的,之所以在后⾯补充2个字节,是因为编译器为了实现结构数组的存取效率,试想如果我们定义了⼀个结构B的数组,那么第⼀个结构起始地址是0没有问题,但是第⼆个结构呢?按照数组的定义,数组中所有元素都是紧挨着的,如果我们不把结构的⼤⼩补充为4的整数倍,那么下⼀个结构的起始地址将是0x0000A,这显然不能满⾜结构的地址对齐了,因此我们要把结构补充成有效对齐⼤⼩的整数倍.其实诸如:对于char型数据,其⾃⾝对齐值为1,对于s
hort型为2,对于int,float,double类型,其⾃⾝对齐值为4,这些已有类型的⾃⾝对齐值也是基于数组考虑的,只是因为这些类型的长度已知了,所以他们的⾃⾝对齐值也就已知了.
同理,分析上⾯例⼦C:
#pragma pack (2) /*指定按2字节对齐*/
struct C
{
char b;
int a;
short c;
};
#pragma pack () /*取消指定对齐,恢复缺省对齐*/
第⼀个变量b的⾃⾝对齐值为1,指定对齐值为2,所以,其有效对齐值为1,假设C从0x0000开始,那么b存放在0x0000,符合
0x0000%1=0;第⼆个变量,⾃⾝对齐值为4,指定对齐值为2,所以有效对齐值为2,所以顺序存放在0x0002、0x0003、0x0004、0x0005四个连续字节中,符合0x0002%2=0。第三个变量c的⾃⾝对齐值为2,所以有效对齐值为2,顺序存放
在0x0006、0x0007中,符合0x0006%2=0。所以从0x0000到0x00007共⼋字节存放的是C的变量。⼜C的⾃⾝对齐值为4,所以C 的有效对齐值为2。⼜8%2=0,C只占⽤0x0000到0x0007的⼋个字节。所以sizeof(struct C)=8.
四.如何修改编译器的默认对齐值?
1.在VC IDE中,可以这样修改:[Project]|[Settings],c/c++选项卡Category的Code Generation选项的Struct Member Alignment中修改,默认是8字节。
2.在编码时,可以这样动态修改:#pragma pack .注意:是pragma⽽不是progma.