Chinaunix首页 | 论坛 | 博客
  • 博客访问: 438618
  • 博文数量: 132
  • 博客积分: 2511
  • 博客等级: 大尉
  • 技术积分: 1385
  • 用 户 组: 普通用户
  • 注册时间: 2006-04-11 15:10
文章分类

全部博文(132)

文章存档

2012年(18)

2011年(35)

2010年(60)

2009年(19)

分类: LINUX

2012-01-06 12:01:17

这段时间,产品的编译环境升级,编译器从sig家的改为gcc 4.5.*,之前产品编译时,都是带-O2选项的,结果有同事发现,之前工作好好的代码出问题了,而且莫明其妙,好像有段代码直接被跳过了,不带-O2编译或者使用-O3都没有该问题。经过定位,发现是由于GCC的strict-aliasing特性引起的,编译时使用fno-strict-aliasing后没有该问题。
之前从没注意过该问题,尤其是写网络程序,自己需要对数据包进行解码时,指针在各个类型中的转换还是有物比较频繁的,现在才知道这个问题,感觉有点土。。
下面的文章很有参考意义。

本文转载,FROM:
http://hi.baidu.com/junru/blog/item/14589545b9bc6f23cffca3dd.html
=============转载开始=======================
玩strict aliasing [转载]
2008年07月08日 星期二 18:57
转自:瀚海星云 ANSIC版 作者:xhacker

一. 不说废话,看代码
zjs@xhacker:/tmp$ cat tt.c
#include

int main()
{
     int a = 0x12345678;
     short *p = (short *)&a;
     short temp;
     temp = *p;
     *p = *(p+1);
     *(p+1) = temp;
     printf("%x\n", a);
}
zjs@xhacker:/tmp$ gcc   -O2   tt.c
zjs@xhacker:/tmp$ ./a.out
12345678
zjs@xhacker:/tmp$ gcc -v
Using built-in specs.
Target: i486-linux-gnu
Configured with: ../src/configure -v --with-pkgversion='Debian 4.3.1-2' --with-bugurl=file:///usr/share/doc/gcc-4.3/README.Bugs --enable-languages=c,c++,fortran,objc,obj-c++ --prefix=/usr --enable-shared --with-system-zlib --libexecdir=/usr/lib
--without-included-gettext --enable-threads=posix --enable-nls --with-gxx-include-dir=/usr/include/c++/4.3 --program-suffix=-4.3 --enable-clocale=gnu --enable-libstdcxx-debug --enable-objc-gc --enable-mpfr --enable-targets=all --enable-cld
--enable-checking=release --build=i486-linux-gnu --host=i486-linux-gnu --target=i486-linux-gnu
Thread model: posix
gcc version 4.3.1 (Debian 4.3.1-2)

上面的指令把环境啥的都揭示的很清楚。这段代码主要是交换一个整数的前一个字和后一
个字,结果很奇怪是不,竟然没交换:D,是不是我土了代码写错?好,再看

zjs@xhacker:/tmp$ gcc tt.c
zjs@xhacker:/tmp$ ./a.out
56781234

awesome!结果完全正确

二。分析
表面上立马看出结果不同的原因在于gcc编译时的参数不同,gcc -O2优化开启了很多
优化选项,可以到gnu网站查的,其中有一项就是-fstrict-aliasing,这开启了
aliasing规则
1.那么什么是aliasing规则呢?先看gcc对-fstrict-aliasing的解释
Allows the compiler to assume the strictest aliasing rules
applicable to the language being compiled. For C (and C++), this
activates optimizations based on the type of expressions. In
particular, an object of one type is assumed never to reside at the same
address as an object of a different type, unless the types are almost
the same. For example, an unsigned int can alias an int, but not a
void* or a double. A character type may alias any other type.

再看c99标准对aliasing的
7 An object shall have its stored value accessed only by an lvalue
    expression that has one of the following types: {footnote 73}

      a type compatible with the effective type of the object,

      a qualified version of a type compatible with the effective type of
      the object,

      a type that is the signed or unsigned type corresponding to the
      effective type of the object,

      a type that is the signed or unsigned type corresponding to a
      qualified version of the effective type of the object,

      an aggregate or union type that includes one of the aforementioned
      types among its members (including, recursively, a member of a
      subaggregate or contained union), or

      a character type.

{footnote 73} The intent of this list is to specify those circumstances
in which an object may or may not be aliased.
gcc的说明更直白点,更容易懂。再看代码
short *p = (short *)&a;该语句中p是指向short的指针,&a是指向int的指针,
这破坏了aliasing规则。

2.破坏aliasing规则为什么会出错呢?
这要从c99为啥提了这么个东东说起,在我理解中,不同类型的指针一定不会指向
同一个内存位置会有两个好处,1是减少可能的load和store;2是编译器可以对指
令做些调整优化。
出错就是因为开启了strict aliasing规则后,gcc认为*p不会指向&a所在的内存位置
所以*p的操作和最后打印a没关系,指令可以reorder。实际上汇编结果表明正是如此
gcc -O2 -S tt.c后tt.s部分内容:
     movl     $305419896, 4(%esp) //可以看出直接送数据,根本不管*p的操作的
     movl     $.LC0, (%esp)
     call     printf

3.c99的restrict关键字
这个就不多说了。它起的作用是表明其修饰的指针不会被其它指针aliased
扩展了strict aliasing
4.对代码作者的影响
对新写的代码,要确保不违反这个规则,那么确实需要让不同指针指向同一个
内存位置怎么办?解决方案参考文献中写得很好,可以看看。当然有不少情形
下不违反这个规则非常麻烦,比如os的内核等特殊地方,那么加入fno-strict
aliasing参数,大家可以发现bsd内核、linux内核编译参数都加了这个

对于已有的代码,违反的地方非常多,那么可以加gcc的-fno-strict-aliasing
参数。

三。问题发现过程
我有时会把linux内核的部分代码用在应用层,linux内核编译时是加了
-fno-strict-aliasing这个参数的(内核编译时加V=1可以看到)
可应用的代码不一定,猜到是和内核的编译参数有关,最后没办法了,对于
所有的参数看着谁像,就去google,最后找到的

四。启发
os内核的编译参数都是各个大牛呕心沥血精心挑选的,选这个参数有他们的用
意,体会这种用意的同时,我们的水平也会提升。这让我想起曾经一款处理器
平台,它的linux模块加载老是不成功,找来找去最后还是找到了编译参数。那
是我第一次和编译参数做斗争:),现在看来和编译参数斗其乐无穷

ps:代码实验必需确保你编译器支持c99标准,而同时有打开和关闭aliasing规则的
参数开关,gcc3.X以后的就可以,微软的不清楚


两个比较好的参考文献
1.
2.
阅读(3082) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~