首先说说为什么要对齐。为了提高效率,计算机从内存中取数据是按照一个固定长度的。以32位机为例,它每次取32个位,也就是4个字节。字节对齐有什么好处?以int型数据为例,如果它在内存中存放的位置按4字节对齐,也就是说1个int的数据全部落在计算机一次取数的区间内,那么只需要取一次就可以了。如果不对齐,很不巧,这个int数据刚好跨越了取数的边界,这样就需要取两次才能把这个int的数据全部取到,这样效率也就降低了。
其实不同的编译器,会对字节对齐进行不同的优化。这里以编译器不对字节对齐做任何优化为前提来讨论。
以下例子均在Ubuntu10.04下按gcc -O0命令编译。
一、原则:
1:当未明确指定时,以结构体中最长的成员的长度为其有效值。
如:
struct AA{
char a;
int b;
char c;
}aa;
char的字对齐长度为1,所以可以在任何地址开始,但是,int自对齐长度为4,必须以4的倍数地址开始。所以,尽管1-3空着,但b也只能从4开始。再加上c后,整个结构体的总长度为9,结构体的有效对齐值为其中最大的成员即int的长度4,所以,结构体的大小向上扩展到12,即9-11的地址空着。
当结构体中包含其他的符合类型时:
数组 :按照基本数据类型对齐,第一个对齐了后面的自然也就对齐了。
联合 :按其包含的长度最大的数据类型对齐。
结构体: 结构体中每个数据类型都要对齐。
2:当用#pragma pack(n)指定时,以n和结构体中最长的成员的长度中较小者为其值。
这个宏定义既限制了整个结构体的对齐方式,也限制了结构体内部的变量的对齐方式。
#pragma pack(2)
struct AA{
char a;
int b;
char c;
}aa;
printf("sizeof aa = %d\n",sizeof(aa)); 结果是8。
假如地址从0开始算起,则变量a占据了头两个字节,变量b占据了接着的4个字节,变量c占据了最后的两个字节。
在这里,变量b按2字节对齐方式,整个结构体也按2字节对齐。
当被#pragma pack限制的结构体1里面包含未被限制的结构体2,则未被限制的结构体2内部按它自己的
规则来决定对齐方式,结构体1把结构体2当作一个一般的变量来处理。例如:
struct N {
short a;
int b;
int c;
};
#pragma pack(2)
struct S {
short a;
int b;
int c;
struct N d;
};
struct N cc;
struct S bb;
printf("sizeof cc = %d\n",sizeof(cc));
printf("sizeof bb = %d\n",sizeof(bb));
打印结果是:
sizeof cc = 12
sizeof bb = 22
因为结构体struct N 没有被宏修饰,所以变量cc内部是按规则1来决定对齐方式,也就是按4字节对齐。
struct S被宏修饰,里面的变量需要和宏比较一下,才能得到各自的对齐方式。里面的变量类型最少占2字节,
最多占12字节,所以里面的变量都按2字节对齐,整个结构体也按2字节对齐。变量d虽然作为一个整个,在
struct S里面按2字节对齐,但它内部还是按4字节对齐,因此变量d还是占据12字节。整个结构体一共占22字节。
3:当用__attribute__ 宏来指定长度时,强制按照此值为结构体的有效对齐。
__attribute__是GNU C的特点,详细请看
http://www.cnblogs.com/astwish/p/3460618.html
__attribute__只修饰该结构体整体,结构体内部的变量是不修饰的。
从连接里面的
printf("oo=%d,xx=%d \n", sizeof(oo),sizeof(xx));
打印结果还可以知道,虽然sizeof(oo)为8,也就是struct o占8个字节,但它是按4字节对齐的。
4:位变量还没研究,以后再补充。