GCC strict aliasing – 嫉妒就是承认自己不如

系统 1764 0

GCC strict aliasing – 嫉妒就是承认自己不如别人

事情是这样的。我们对tair(淘宝的分布式Key/Value系统)动了一次大手术,更换了网络框架,经过长时间的测试/调试,终于完全通过了回归测试。但要打包发布的时候,却发现服务器可以正常启动,但却完全无法接受请求。调试无果,对比打包前后程序的差异,仅在于是否使用-O2选项对程序进行编译优化。
无头苍蝇一样,Google搜索“gcc optimization problems”,找到StackOverflow上面的 这个帖子 ,“抱着试试看的心态”,在编译选项中加入-fno-strict-aliasing,bingo!
-fno-strict-aliasing这个选项是做什么的?aliasing又是什么?C和C++的标准给出了说明:

        Strict aliasing is an assumption, made by the C (or C++) compiler, that dereferencing pointers to 
objects of different types will never refer to the same memory location (i.e. alias eachother.)

      

即是说,在strict aliasing规则下,C/C++编译器认为,“不同类型”的指针(准确说是lvalue)一定不会引用同一个内存区域(即aliasing)。在这个规则的前提下,编译器就可以进行相应的优化。看下面这个函数:

                  1
2
3
4
5
6
7
8
9
10

                
                  
                    int
                  
                   n
                  
                    ;
                  
                  
                    int
                  
                   foo
                  
                    (
                  
                  
                    int
                  
                  
                    *
                  
                  ptr
                  
                    )
                  
                  
                    {
                  
                  
  n
                  
                    =
                  
                  
                    1
                  
                  
                    ;
                  
                  
                    *
                  
                  ptr
                  
                    =
                  
                  
                    3
                  
                  
                    ;
                  
                  
                    return
                  
                   n
                  
                    ;
                  
                  
                    }
                  
                  
                    int
                  
                   main
                  
                    (
                  
                  
                    )
                  
                  
                    {
                  
                  
  fprintf
                  
                    (
                  
                  stdout
                  
                    ,
                  
                  
                    "%d
                    
                      \n
                    
                    "
                  
                  
                    ,
                  
                   foo
                  
                    (
                  
                  
                    &
                  
                  n
                  
                    )
                  
                  
                    )
                  
                  
                    ;
                  
                  
                    return
                  
                  
                    0
                  
                  
                    ;
                  
                  
                    }
                  
                

编译并运行:

                  1
2
3
4

                
                  $ 
                  
                    cc
                  
                   main.c 
                  
                    &&
                  
                   .
                  
                    /
                  
                  a.out

                  
                    3
                  
                  
$ 
                  
                    cc
                  
                   main.c 
                  
                    -O2
                  
                  
                    &&
                  
                   .
                  
                    /
                  
                  a.out

                  
                    3
                  
                

一切正常,不是吗?但如果把函数foo的参数类型改作double*,运行结果“可能”会是:

                  1
2
3
4
5
6

                
                  $ 
                  
                    cc
                  
                   main.c 
                  
                    &&
                  
                   .
                  
                    /
                  
                  a.out

                  
                    3
                  
                  
$ 
                  
                    cc
                  
                   main.c 
                  
                    -O2
                  
                  
                    &&
                  
                   .
                  
                    /
                  
                  a.out

                  
                    1
                  
                  
$ 
                  
                    cc
                  
                   main.c 
                  
                    -O2
                  
                  
                    -fno-strict-aliasing
                  
                  
                    &&
                  
                   .
                  
                    /
                  
                  a.out

                  
                    3
                  
                

在加-O2选项的情况下程序编译该程序,输出竟然是1,难道*ptr=3没有被执行吗?不是的,*ptr=3确实是执行了的,全局变量n在函数返回时也确实已经是3了(你可以在fprintf之后打印出n值做验证),但是foo函数中的语句return n却被优化成了return 1。为什么呢?因为后者比前者稍了一次内存访问。编译器为什么做这样的优化,为什么在ptr为int*时不做此优化?
这就涉及到strict aliasing的具体规则了。首先定义一下alias:两个不同的变量引用了同一个对象(内存区域),那么就称这两个变量互为alias。下面是C99中可以互为alias的所有情况,除此之外的其他情况下,如果编译时指定-fstrict-aliasing(-O2及以上优化时自动指定),那么就执行strict aliasing:

  • 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),
  • a character type.

大致是这样的:两个类型兼容的变量可以互为alias,即使使用了signed/unsigned和const/volatile等修饰符;一个类型可以与另一个包含与该类型兼容的成员的struct/union类型的变量互为alias;char类型可以与任意类型互为alias。C++中可以互为alias的还可以是父类与子类。

可以使用-fno-strict-aliasing来取消strict aliasing规则,同时也就放弃了这个规则带来的优化空间,放弃了一定的性能提升。如果你也遇到了文章开头我遇到的问题,而且担心-fno-strict-aliasing的性能损失,那就只能找出违反规则的代码,调整该代码,或者仅仅取消该代码的strict aliasing。

基本就是这样了,最后总结一下。GCC的aliasing与优化紧密相关,在指定-O2及以上优化级别时自动打开-fstrict-aliasing,执行strict aliasing规则以优化编译程序。如果你的程序不遵守该规则(比如上面foo函数中出现double*ptr和n同时应用n的情况),就极有可能受到惩罚。GCC中与strict aliasing相关的选项除了-fstrict-aliasing/-fno-strict-aliasing,还有warning选项-Wstrict-aliasing=n,这个选项在你违反stict aliasing时给出警告,其中n为检查的力度,一般设为2。

最后,如果想深入了解strict aliasing,可以参考这篇 Understanding Strict Aliasing 。另外,GCC的官方文档中有和 优化选项相关的描述 ,其中也提到了strict aliasing。

GCC strict aliasing – 嫉妒就是承认自己不如别人


更多文章、技术交流、商务合作、联系博主

微信扫码或搜索:z360901061

微信扫一扫加我为好友

QQ号联系: 360901061

您的支持是博主写作最大的动力,如果您喜欢我的文章,感觉我的文章对您有帮助,请用微信扫描下面二维码支持博主2元、5元、10元、20元等您想捐的金额吧,狠狠点击下面给点支持吧,站长非常感激您!手机微信长按不能支付解决办法:请将微信支付二维码保存到相册,切换到微信,然后点击微信右上角扫一扫功能,选择支付二维码完成支付。

【本文对您有帮助就好】

您的支持是博主写作最大的动力,如果您喜欢我的文章,感觉我的文章对您有帮助,请用微信扫描上面二维码支持博主2元、5元、10元、自定义金额等您想捐的金额吧,站长会非常 感谢您的哦!!!

发表我的评论
最新评论 总共0条评论