好高兴
上一篇 /
下一篇 2008-04-25 09:11:16
对齐是跟数据块在内存中的位置相关的话题。如果一个变量的内存地址正好是它长度的整数倍,它就被称作是自然对齐的。举例来说,对于一个32位类型的数据,如果它在内存中的地址刚好可以被4整除(也就最低两位为0),那它就是自然对齐的。也就是说,一个大小为2n字节的数据类型n,它地址的最低有效位的后n位都应该为0。
BMk+C'I189102一些体系结构对对齐的要求非常严格。通常象基于RISC的系统,载入未对齐的数据会导致处理器陷入(一种可处理的错误)。还有一些系统可以访问没有对齐的数据,只不过性能会下降。编写可移植性高的代码要避免对齐问题,保证所有的类型都能够自然对齐。
7h2l H/u!h189102
*Tg:d aKc)q1891021. 避免对齐引发的问题LUPA开源社区1ZeJ-U}AU
编译器通常会通过让所有的数据自然对齐来避免引发对齐问题。实际上,内核开发者在对齐上不用花费太大心思—只有搞gcc的那些老兄才应该为此犯愁呢。可是,当程序员使用 指针太多,对数据的访问方式超出编译器的预期时,就会引发问题了。LUPA开源社区+qQ?-Y M ly&B
一个数据类型长度较小,它本来是对齐的,如果你用一个指针进行类型转换,并且转换后的类型长度较大,那么通过改指针进行数据访问时就会引发对齐问题(无论如何,对于某些体系结构会存在这种问题)。也就是说,下面的代码是错误的:LUPA开源社区 h yD/DM"_| ga
char dog[10];
$WOI;cV`189102char *p = &dog[1];LUPA开源社区6o Gg)R7dkQ
unsigned long l = *(unsigned long *)p;LUPA开源社区 [[8ni P&s#ljz&s
这个例子将一个指向char型的指针当作指向unsigned long型的指针来用,这会引起问题,因为此时会试图从一个并不能被4整除的内存地址上载入32位的unsigned long型数据。LUPA开源社区#_,^`\7_ Ar
如果你能想到“我会在现实中这么做吗?”,你基本上就不会有问题了。无论如何,这种错误出现了,并且它还会发生,所以应该小心。实际编程时错误可能不会像这个例子中这么明显。LUPA开源社区,[F:r3x*? i2j
R[haxt3`1891022. 非标准类型的对齐
(T G@A&eTh:I189102前面提到了,对于标准数据类型来说,它的地址只要是其长度的整数倍就对齐了。而非标准的(复合的)C数据类型按照下列原则对齐:
!qxPNo0ECC189102* 对于数组,只要按照基本数据类型进行对齐就可了(其实随后的所有元素自然能够对齐)。LUPA开源社区W/EGEq8e c
* 对于联合,只要它包含的长度最大的数据类型能够对齐就可以了。
]Z PWyAM:B6C189102* 对于结构体,只要它包含的长度最大的数据类型能够对齐就可以了。
4{8sJI0Jq6j vR r189102结构体还要引入填补机制,这会引出下一个问题。
6J$Q'Ra9rSv9D FH189102LUPA开源社区 IEjg,Ve&a O1F?;^
3. 结构体填补
*@C?[1m h{7U$k189102为了保证结构体中每一个成员都能够自然对齐,结构体要被填补。这点确保了当处理器访问结构中一个给定元素时,元素本身被对齐。This ensures that when the processor accesses a given element in the structure, that element itself is aligned.举个例子,下面是一个在32位机上的结构体:
'U0^3d*B)T~i0j3W}189102struct animal_struct {LUPA开源社区Q J;E2]b5W Acv2^
char dog; /* 1字节*/
5Y4^)~6mUd_ a I#w189102 unsigned long cat; /* 4字节*/LUPA开源社区6n t-LK} ^0p|J
unsigned short pig; /* 2字节 */
0?*Wd/SJ4[Law189102 char fox; /* 1字节*/
B YT$L u'T]189102};LUPA开源社区:]J&p4Z f(gI)j1co
由于该结构不能准确地满足各个成员自然对齐,所以它在内存中可不是按照原样存放的。编译器会在内存中创建一个类似下面给出的结构体:LUPA开源社区0ul"~QV*U
struct animal _struct {
2W)lP2A`189102 char dog; /* 1字节*/LUPA开源社区O W] qH-f2c
u8 __pad0[3]; /* 3字节*/LUPA开源社区XE6?i4V/{
unsigned long cat; /* 4字节*/
9u.A6A5`"v0_189102 unsigned short pig; /* 2字节 */
a#] AlBzTO)K ~%A189102 char fox; /* 1字节*/
&ctkc%^189102 u8 __pad1; /* 1字节*/LUPA开源社区b {{`S
};LUPA开源社区]:k&],dP)aAF
填补的变量都是为了能够让数据自然对齐而加入的。第一个填充物占用了3个字节的空间,保证cat可以按照4字节对齐。这也自动使其他小的对象都被对齐了,因为它们长度都比cat要小。第二个也是最后的填充是为了填补struct本身的大小。额外的这个填补使结构体的长度能够被4整除,这样,在由该结构体构成的数组中,每个数组项也就会自然对齐了。
3R+F_gs189102注意,在大部分32位系统上,对于任何一个这样的结构体,sizeof(animal_struct)都会返回12。C编译器自动进行填补以保证自然对齐。LUPA开源社区t7bv^(rFHA
通常你可以通过重新排列结构中的对象来避免填充。这样既可以得到一个较小的结构体,又能保证无需填补它也是自然对齐的。
;[$K9X$~ zvUD c7V'f189102struct animal _struct {
;ER#QC v189102 unsigned long cat; /* 4字节*/
J$B0tJA LK,C8]189102 unsigned short pig; /* 2字节 */
wj/^sV7S G189102 char dog; /* 1字节*/LUPA开源社区|Q.T;K7?tT*?o
char fox; /* 1字节*/
f/h(I(F0kw H%M ?189102};
9[Cm1mBC In189102现在这个结构体只有8字节大小了。不过,不是任何时候都可以这样对结构体进行调整的。举个例子,如果该结构体是为某个标准的一部分,或者它是现有代码的一部分,那么它的成员次序就已经被定死了,虽然在内核中(缺少一个正式的ABI)相比用户空间来说,这种需求要少的多。还有些时候,你因为一些原因必须使用某种固定的次序—比如说,为了提高高速缓存的命中率进行优化时设定的变量次序。注意,ANSI C明确规定不允许编译器改变结构体内成员对象的次序[1]——它总是由你,程序员来决定。虽然编译器可以帮助你做填充,但是,如果使用-Wpadded标志,那么将使gcc在发现结构体被填充时产生警告。
|9f^p x-m(r#[r&V189102内核开发者需要注意结构体填补问题,特别是在整体使用时—这是指当需要通过网络发送它们或需要将它们写入文件的时候,因为不同体系结构之间所需要的填补也不尽相同。这也是为什么C没有提供一个内建的结构体比较操作符的原因之一。结构体内的填充字节中可能会包含垃圾信息,所以在结构体之间进行一字节一字节的比较就不大可能实现了。C语言的设计者(正确的)感觉到最好还是由程序员自己为不同的情况编写比较函数,这样才能利用到结构体次序信息。
$JM#}6vC_ |189102
+v0fxw-V X'K189102[1] 如果让编译器随心所欲地改变结构体中各个对象地位置的话,现存的程序大部分都会崩溃。在C语言中,函数往往通过在结构体地址上加上偏移量来计算变量的位置。
导入论坛
收藏
分享给好友
管理
举报
TAG: