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。
更多文章、技術交流、商務合作、聯系博主
微信掃碼或搜索:z360901061

微信掃一掃加我為好友
QQ號聯系: 360901061
您的支持是博主寫作最大的動力,如果您喜歡我的文章,感覺我的文章對您有幫助,請用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點擊下面給點支持吧,站長非常感激您!手機微信長按不能支付解決辦法:請將微信支付二維碼保存到相冊,切換到微信,然后點擊微信右上角掃一掃功能,選擇支付二維碼完成支付。
【本文對您有幫助就好】元
