[英]Strange GCC6 optimization with builtin function
使用GCC6和下面的代碼段,此測試
if (i > 31 || i < 0) {
為false ,並且此printf被執行
printf("i > 31 || i < 0 is FALSE, where i=%d", i);
並產生以下非常奇怪的輸出(GCC6):
i> 31 || i <0是FALSE ,其中i = 32 / * GCC6的結果很奇怪! * /
而使用GCC4我得到:
i> 31 || i <0為true ,其中i = 32 / *結果與GCC4一致* /
看起來還不錯
怎么會這樣??
static int check_params(... input parameters ...) {
/* Note that value can be 0 (zero) */
uint32_t value = ....
int i;
i = __builtin_ctz(value);
if (i > 31 || i < 0) {
printf("i > 31 || i < 0 is true, where i=%d", i);
/* No 1 found */
return 0;
} else {
printf("i > 31 || i < 0 is FALSE, where i=%d", i);
}
return i;
}
根據有關GCC內置函數的文檔 ,必須避免調用__builtin_ctz(0):
內置函數:int __builtin_ctz (無符號int x)返回x中從最低有效位開始的尾隨0位的數目。 如果x為0, 則結果不確定 。
因此,顯然,解決編碼錯誤的方法是在調用__builtin_ctz(value)之前簡單地檢查該值。 這是清楚的,可以理解的。
我可以停在那里,然后轉到其他主題... 但是,我仍然不明白(使用破損的代碼)如何獲得以下輸出:
i> 31 || i <0是FALSE ,其中i = 32 / * GCC6的結果很奇怪! * /
奇怪的GCC6優化之類的東西?
以防萬一重要:
Cross-compiler: arm-linux-gcc
Architecture: -march=armv7-a
任何想法?
除非未定義行為,否則__builtin_ctz
將始終返回0到31之間的數字,並且GCC知道這一點。 因此檢查i > 31 || i < 0
i > 31 || i < 0
始終為假(再次禁止未定義的行為),並且可以進行優化。
如果查看生成的程序集,您會發現該條件根本沒有出現在代碼中(當時的情況也是如此)。
未定義的行為並不意味着“該值將是任意的”。 這意味着編譯器實際上可以執行其想要執行的任何操作 。 在這種情況下,看起來編譯器能夠靜態驗證只要value
不為0 , i
將始終介於0和31之間(含0和31)。 因此,它甚至不必為隨后的子句生成代碼。
你只是幸運的惡魔沒有從你的鼻子里出來。
另請參見: 未定義的行為可能導致時間旅行 , 過早的垂頭喪氣 , 未定義的行為為何可能調用永不調用的函數的原因 ,以及UB的許多其他許多討論 。
編譯器假定未發生未定義行為。 之所以可以做出這樣的假設,是因為如果違反了約束且行為未定義,則任何結果都是可能的,包括由錯誤的假設導致的結果。
如果沒有未定義的行為,則i
不能為負或大於31。在此基礎上,可以在編譯時優化if
語句中的條件。
由printf
實際打印的值無法預測,因此實際上i
會碰巧調用printf
。 在這種情況下,它碰巧是32,但可能是任何東西。
C標准指出,實現將“未定義行為”視為邀請編譯器以環境的書面形式記錄為特征的行為是很常見的,並且“原理”指出,將行為分類為UB旨在導致高質量實現提供超越功能的特性。當市場需要時,由標准強制要求的產品。 但是,從他們的行動來看,一些編譯器供應商認為,讓編譯器尋找巧妙的方法來利用無意義地處理某些案件的自由性,比在可行時采用理性約束的行為(例如,擁有__builtin_ctz( )以未指定的方式在執行CPU操作或生成任意值之間進行選擇。
我個人比較懷疑是否通過允許編譯器執行任何操作而不是產生值或執行CPU操作而帶來任何“優化”,其自然結果導致的價值是否接近允許程序員承擔的價值。在CPU本身不做任何奇怪處理的平台上,該操作將僅產生未指定的結果且沒有副作用。 盡管如此,gcc的作者似乎認為,要求程序員添加額外的源代碼來處理可容納的情況,而這些情況無需額外的機器代碼就可以以某種方式提高“效率”。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.