一、概念
对齐跟数据在内存中的位置有关。程序中如果一个变量的内存地址正好位于它长度的整数倍,它就被称做自然对齐。比如在32位cpu下,假设一个整型变量的地址为0x00000004,那它就是自然对齐的。
二、为什么要字节对齐
字节对齐和CPU的数据访问效率是有关系的。假设整型变量的地址不是自然对齐,比如为0x00000002,则CPU如果取它的值的话需要访问两次内存:第一次取从0x00000002——0x00000003的一个short,第二次取从0x00000004——0x00000005的一个short,然后组合得到所要的数据。如果变量在0x00000003地址上的话则要访问三次内存,第一次为char,第二次为short,第三次为char,然后组合得到整型数据。如果变量在自然对齐位置上,则只要一次就可以取出数据。
一些系统对对齐要求非常严格,如果取未对齐的数据会发生错误,举个例:
char data_buf[5];
char *p = &data_buf[0];
int i = *(int *)p;
在一些RISC系统运行时会报segment error,而在x86上就不会出现错误,只是效率下降。
三、正确处理字节对齐
对于标准数据类型,它的地址只要是它的长度的整数倍就行了,而非标准数据类型按下面的原则对齐:
数组 :按照基本数据类型对齐,第一个对齐了后面的自然也就对齐了。
联合体 :按其包含的长度最大的数据类型对齐。
结构体: 结构体中每个数据类型都要对齐。
比如有如下一个结构体:
struct test{
char a;
int b;
char c[10];
};
struct test test_str;
由于在x86下,GCC默认按4字节对齐,它会在a后面跟c后面分别填充三个和两个字节使b和整个结构体对齐。于是我们sizeof(test_str)会得到长度为20,而不是15。
(PS:char a,1个字节,补上3个所以为4个字节;int b,4个字节;char c[10],10个字节,补上两个,为12字节,刚好是4的倍数,所以全部加起来就为4 + 4 + 12 = 20字节。)
四、__attribute__选项
我们可以按照自己设定的对齐大小来编译程序,GNU使用__attribute__选项来设置,比如我们想让刚才的结构按一字节对齐,我们可以这样定义结构体
struct test{
char a;
int b;
char c[10];
}__attribute__ ((aligned (1)));
struct test test_str;
则sizeof(test_str)可以得到大小为15。
上面的定义等同于
struct test{
char a;
int b;
char c[10];
}__attribute__ ((packed));
struct test test_str;
__attribute__((packed))得变量或者结构体成员使用最小的对齐方式,即对变量是一字节对齐,对域(field)是位对齐。
五、什么时候需要设置对齐
在设计不同CPU下的通信协议时,或者编写硬件驱动程序时寄存器的结构这两个地方都需要按一字节对齐。即使看起来本来就自然对齐的也要使其对齐,以免不同的编译器生成的代码不一样。