为什么要字节对齐,用意何在?
如果一个变量的内存地址正好位于它长度的整数倍,他就被称做自然对齐。比如在32位cpu下,假设一个整型变量的地址为0x00000004,那它就是自然对齐的。需要字节对齐的根本原因在于CPU访问数据的效率问题。假设之前整型变量的地址不是自然对齐,比如为0x00000002,则CPU如果取它的值的话需要访问两次内存,第一次取从0x00000002-0x00000003的一个short,第二次取从0x00000004-0x00000005的一个short然后组合得到所要的数据,如果变量在0x00000003地址上的话则要访问三次内存,第一次为char,第二次为short,第三次为char,然后组合得到整型数据。而如果变量在自然对齐位置上,则只要一次就可以取出数据。所以字节对齐可以提高效率。当然对齐的工作编译器会帮我自动完成。
字节对齐在结构体中比较常见,
struct stu{
char sex;
int length;
char name[10];
};
一般GCC或者其他编译器默认结构体中所占内存最大的字节数对齐(例如上面结构体length最大占4个字节,所以结构体按照4字节对齐,如果int换成short则是按照2字节对齐,换成double则是按照8字节对齐,注意数组则是按照总体大小是否能被对齐大小整除,如果不能则填充直到能被整除),它会在sex后面跟name后面分别填充三个和两个字节使length和整个结构体对齐。于是我们sizeof(my_stu)会得到长度为20,而不是15。
那么如何制定对齐的大小呢?
struct stu{
char sex;
int length;
char name[10];
}__attribute__ ((aligned (1)));
GNU使用__attribute__选项来设置,理论上上面的结构体这样定义的话大小应该是15而不是20,但实际测试发现仍然是20(??),但是换成_attribute__ ((packed))或者下面所说的pack指令则起了作用使得变量或者结构体成员使用最小的对齐方式。(理论上_attribute__ ((packed))等价于__attribute__ ((aligned (1))))。
也可以使用使用伪指令#pragma pack (n),使得C编译器将按照n个字节对齐。
然后在结尾处使用伪指令#pragma pack (),取消自定义字节对齐方式。
注意无论是哪种方法,对于结构体而言对齐大小都只针对1和偶数对齐有效,并且设置超过元素所占最大内存也会无效,例如在上面的结构体设置8字节对齐,但实际仍然还是4字节对齐。3字节对齐仍然还是4字节对齐。
注意:实际测试发现__attribute__ ((aligned (n)))这种表达式效果不是很好,例如同样设置2字节对齐,使用__attribute__ ((aligned (2)))后上面结构体大小仍然为20字节而使用pack则为预想的16字节。
既然编译器已经帮我完成了字节对齐的工作,那么我们为什么还要注意呢?其答案在与节约内存。
struct A {
char b;
short c;
int a;
};
struct B {
char b;
int a;
short c;
};
同样的成员变量,但是不同的写法,所占的内存不一样,结构体A大小为8个字节,而结构体B大小为12个字节。
所以针对字节对齐,我们在编程中如何考虑?
如果在编程的时候要考虑节约空间的话,那么我们只需要假定结构的首地址是0,然后各个变量按照上面的原则进行排列即可,基本的原则就是把结构中的变量按照类型大小从小到大声明,尽量减少中间的填补空间.还有一种就是为了以空间换取时间的效率,我们显示的进行填补空间进行对齐,比如:有一种使用空间换时间做法是显式的插入reserved成员:
struct A{
char a;
char reserved[3];//使用空间换时间
int b;
}
reserved成员对我们的程序没有什么意义,它只是起到填补空间以达到字节对齐的目的,当然即使不加这个成员通常编译器也会给我们自动填补对齐,我们自己加上它只是起到显式的提醒作用。