所谓的字节对齐,就是各种类型的数据按照一定的规则在空间上排列,而不是顺序的一个接一个的排放,这个就是对齐。我们经常听说的对齐在N上,它的含义就是数据的存放起始地址%N==0。具体对齐规则会在下面的篇幅中介绍。首先还是让我们来看一下,为什么要进行字节对齐吧。
各个硬件平台对存储空间的处理上有很大的不同。一些平台对某些特定类型的数据只能从某些特定地址开始存取。比如有些架构的CPU,诸如SPARC在访问一个没有进行对齐的变量的时候会发生错误,那么在这种架构上必须编程必须保证字节对齐。而有些平台对于没有进行对齐的数据进行存取时会产生效率的下降。让我们来以x86为例看一下如果在不进行对齐的情况下,会带来什么样子的效率低下问题,看下面的数据结构声明:
view plaincopy to clipboardprint?
01.struct A {
02. char c;
03. int i;
04.};
05.struct A a;
Code 13-1
假设变量a存放在内存中的起始地址为0x00,那么其成员变量c的起始地址为0x00,成员变量i的起始地址为0x01,变量a一共占用了5个字节。当CPU要对成员变量c进行访问时,只需要一个读周期即可。而如若要对成员变量i进行访问,那么情况就变得有点复杂了,首先CPU用了一个读周期,从0x00处读取了4个字节(注意由于是32位架构),然后将0x01-0x03的3个字节暂存,接着又花费了一个读周期读取了从0x04-0x07的4字节数据,将0x04这个字节与刚刚暂存的3个字节进行拼接从而读取到成员变量i的值。为了读取这个成员变量i,CPU花费了整整2个读周期。试想一下,如果数据成员i的起始地址被放在了0x04处,那么读取其所花费的周期就变成了1,显然引入字节对齐可以避免读取效率的下降,但这同时也浪费了3个字节的空间(0x01-0x03)。
有了上述的基本概念之后,让我们来看一下,编译器是按照什么样的原则进行对齐的。首先有3个重要的概念:自身对齐值,指定对齐值和有效对齐值。
自身对齐值:即数据类型的自身的对齐值。例如char型的数据,其自身对齐值为1字节;short型的数据,其自身对齐值为2字节;int,float,long类型,其自身对齐值为4字节;double类型,其自身对齐值为4字节;而struct和class类型的数据其自身对齐值为其成员变量中自身对齐值最大的那个值。
指定对齐值:#pragma pack (value)时指定的对齐值value
有效对齐值:上述两个对齐值中最小的那个。
我们一般说的对齐在N上,都是指有效对齐在N上。说了这么多,还是让我们先来看一些例子来加深对这些概念的理解吧。例:假设在x86机器上,假设编译器按默认4字节进行对齐
view plaincopy to clipboardprint?
01.struct A {
02. char c;
03. int i;
04. short s;
05.};
06.
07.#pragma pack (2) /* 指定按2字节对齐 */
08.struct B {
09. char c;
10. short s;
11. int i;
12.};
13.#pragma pack () /* 恢复默认对齐 */
Code 13-2
让我们来考虑一下sizeof(struct A)和sizeof(struct B)的结果各应该是什么。首先来看sizeof(struct A),假设A的起始地址为0x00,做这样的假设只是为了更方便理解,其实A始终被放在对齐边界上,这并不影响sizeof的结果,在接下来的例子中,我们也会继续沿用这个假设。言归正传,数据成员c的自身对齐值=1,指定对齐值=4(默认),所以其有效对齐值为1,因0x00%1==0,所以它被存放在0x00处;数据成员i的自身对齐值=4,指定对齐值=4,可得出其有效对齐值为4,因0x01%4 != 0,因此它应该被存放在0x04地址处,占用0x05,0x06,0x07共4个字节;接下来看数据成员s的自身对齐值=2,指定对齐值=4,得出有效对齐值为2,因0x08%2 == 0,因此它被存放在起始地址为0x08处,并占用2字节;最后再看数据结构A自身的对齐值=4(最大数据成员自身对齐值),指定对齐值=4,得有效对齐值为4,因0x0A%4 != 0,因此多占用0x0A和0x0B为结构体A所用(这一步的作用是基于结构体数组的出发,对于结构体或者类,要将它们补充成其有效对齐值的整数倍,这点请千万注意)。由此可见sizeof(struct A)的结果应该是=1+3(空闲空间)+4+2+2(结构体补充)=12字节。
接下来让我们考察sizeof(struct B)的结果。这里需要注意的是,在B被声明前,指定对齐值已经被设置为2个字节。数据成员c的有效对齐值为1,存放起址0x00,s的有效对齐值为2,存放起址0x02,i的有效对齐值也为2,存放起址为0x04,累加起来一共是8个字节,已经是数据结构B的有效对齐值2的整数倍了。因此sizeof(struct B)的结果8个字节。
看到这里,应该对字节对齐有了一定的了解了吧。接下来我们要看一个更加复杂的例子:假设在x86机器上,假设编译器按默认4字节进行对齐
view plaincopy to clipboardprint?
01.#pragma pack(8)
02.struct S1 {
03. char a;
04. long b;
05.};
06.
07.struct S2 {
08. char c;
09. struct S1 d;
10. long long e;
11.};
12.#pragma pack()
13.
Code 13-3
运用上面所学到的知识,应该不难得出sizeof(struct S1)的值为8字节,其中a的有效对齐值为1,b的有效对齐值为4,结构S1的有效对齐值为4。现在让我们来看看sizeof(struct S2)的值会是多少呢?首先成员c的有效对齐值为1,S1的自身对齐值为成员的最大自身对齐值,即4字节,其指定对齐值为8,则其有效对齐值也为4,存放起址应该为0x04,并且占用8个字节(0x04+0x08=0x0C),其中0x01-0x03被用来填充。接下去数据成员e的有效对齐值为4,存放起址应该是0x0D % 8 == 0,占用8个字节(0x10)。最后考察S2本身的有效对齐值应该是4字节,而0x0D%8==0,就不需要填充了,因此sizeof(struct S2)=20。
在vc6工具中,我们可以选择[project]->[Settings]->[C/C++]->[Code Generation]->[Struct member alignment]来更改默认对齐字节数。
参考:http://www.sco.com/developers/devspecs/abi386-4.pdf