2009-11-24 145 views
8

當寫這樣的:條件運算符是否應該評估所有參數?

1: inline double f(double arg) { 
2: return arg == 0.0 ? 0.0 : 1./arg; 
3: } 
4: const double d = f(0.0); 

微軟的Visual Studio 2005的64位編譯器

line 4: warning C4723: potential divide by 0 

來雖然你和我可以清楚地看到,一個div被零是永遠去發生...

或者是它?

+11

保重,比較關於平等的「雙重」論點。邪惡的魔法發生在那裏...... – SadSido 2009-11-24 08:16:58

+1

不,它不。完美定義的過程。特別是'0.0 == -0.0'。因此,對於定義了1./arg的所有值集合,我們知道'arg!= 0.0'。 – MSalters 2009-11-26 15:04:25

+1

@ MSalters:但由於四捨五入錯誤,arg可能不是0.0(或-0.0),如果您期望。 – jalf 2009-11-26 15:31:06

回答

4

這是一個明顯的錯誤,毫無疑問。

警告的目的不是要警告程序中的所有部門。這在任何合理的計劃中都會太吵。相反,當你需要檢查一個參數時,意圖是警告你。在這種情況下,你確實檢查了參數。因此,編譯器應該注意到,並閉嘴。

這種功能的技術實現是通過在具有特定屬性的代碼分支中標記變量來完成的。最常見的屬性之一是三態「無效」。在分支之前,arg是外部變量,arg [[Isnull]]未知。但在arg檢查後有兩個分支。在第一個分支arg [[Isnull]]是真的。在第二個分支arg [[Isnull]]是錯誤的。

現在,當涉及到產生零分和空指針警告時,應該檢查[[IsNull]屬性。如果屬實,則有嚴重的警告/錯誤。如果不知道,則應該生成上面顯示的警告 - 這是一個潛在的問題,超出編譯器可以證明的範圍。但在這種情況下,[[isNull]]屬性爲False。編譯器通過與人類相同的形式邏輯,知道沒有風險。

但是,我們如何知道編譯器在內部使用了這樣的[[Isnull]]屬性?回想一下第一段:沒有它,它必須總是或永遠警告。我們知道它有時會發出警告,但必須有[[IsNull]]屬性。

+0

這是可靠的推理。我可以接受這一點 - 尤其是因爲它證明了我的直覺。 – xtofl 2009-11-26 19:02:13

+0

最近的一個Linux bug是由這個屬性造成的。在有問題的代碼,指針被解除引用第一(基本上'INT *構件=(foo->巴);'然後檢查'如果(富)回報;!'然而,因爲取消引用時,'FOO [的。 [IsNull]]'屬性已被設置爲false,並優化NULL檢查。 – MSalters 2009-11-27 10:14:21

11

編譯器無法靜態分析所有代碼路徑並始終考慮所有可能性。理論上,僅通過查看其源代碼就可以完全分析程序行爲,這可以提供一個解決問題的方法,這是不可判定的。編譯器有一套有限的靜態分析規則來檢測規則。 C++標準不要求編譯器發佈這樣的警告,所以不需要。這不是一個錯誤。這更像是一個不存在的功能。

+0

你說得對。從C++標準的角度來看,它永遠不會是一個錯誤,因爲它涉及警告。 但是,如果這個「漏洞」迫使我重寫完全有效的代碼(或'#pragma's圍繞着它),從一個「計算機程序的角度,這是一個錯誤。 – xtofl 2009-11-24 08:07:14

+0

@xtofl:警告中的關鍵詞是「潛在的」 – 2009-11-24 08:08:08

+0

同意了,但關鍵是痛苦,我們試圖警告視爲錯誤,並不能與「假陽性」警告這樣做。 – xtofl 2009-11-24 08:15:35

7

不,條件運算符不會評估這兩個參數。但是,如果編譯器可以檢測到這種情況,通常會報告一個可能被零除的值。標準佔用大約2頁來描述這個操作符的行爲並不是假的。

從N 4411:

5.16條件運算符

條件表達式組 從右到左。第一個表達式 上下文轉換爲bool(條款 4)。它被評估,如果它是真的, 條件 表達式的結果是第二個 表達式的值,否則 第三個表達式的值。 第二個和第三個表達式中只有一個是 評估。在與第二或第三 表達式相關的每個 值計算和副作用 之前,對與第一個 表達式相關聯的每個值計算和副作用進行排序。

此外,請注意:

否則,如果第二和第三 操作數有不同的類型,並且 或者已經(可能CV修飾) 類類型,試圖到 將這些操作數的每一個轉換爲另一個的 類型。

您引用的例子對於第二個和第三個表達式都具有相同的類型 - 放心,只有第一個將被評估。

+0

我懷疑第3點實際上說兩個操作數都會被評估。我認爲這是關於是否像'std :: string s(「A」); const char * c =「B」; std :: string a = cond()? s:c; const char * b = cond()? s:c;'應該編譯或不編譯(確定運算符的結果類型應該是什麼:編譯器檢查什麼可以轉換爲什麼 - 在本例中是第一個::編譯,因爲const char *可以隱式轉換到std :: string的,但第二個不編譯的,因爲結果類型操作的是'的std :: string',而這不能被隱式轉換爲'爲const char *') – UncleBens 2009-11-24 16:11:25

+0

Accpeted和編輯。我應該指出這一點。 – dirkgently 2009-11-24 17:23:43

3

該部門的代碼將生成,因此警告。但arg爲0時決不會採用該分支,因此安全。

3

運算符==對於浮點數是不安全的(即由於舍入問題您不能信任它)。在這個特定的情況下,它實際上是安全的,所以你可以忽略這個警告,但編譯器不會根據一般情況下的結果有點不可預測的運算符進行這樣的分析。

3

條件運算符不應該評估所有參數。但我相信你可以把arg幾乎等於0,所以arg == 0.0將是false,但是1./arg會給出「除以零」的結果。所以我認爲這個警告在這裏很有用。

順便說一下,Visual C++ 2008不會給出這樣的警告。

+0

這是有道理的。謝謝。 – xtofl 2009-11-24 09:31:28

+1

C中的IEEE 754浮點除法不會除以零。除以零的結果將是正無窮,負無窮大或NaN – 2009-11-24 11:52:01

+0

結果可以是正無窮大或負無窮大,對於次正規數,但我會稱之爲溢出而不是零師。 – starblue 2009-11-24 13:14:04

0

除了其他註釋:警告是由編譯器生成的,死的分支由優化器刪除,稍後運行 - 甚至可能在鏈接階段。

所以不,這不是一個錯誤。警告是由編譯器提供的附加服務,不是標準的要求。這是編譯器/鏈接器體系結構的一個不幸的副作用。

0

您可能能夠避免使用特定於Microsoft的__assume關鍵字的警告。我不確定您是否可以將它與條件運算符綁定。否則就像

if (arg == 0.0){ 
    return 0.0; 
} 
else { 
__assume(arg != 0.0); 
    return 1./arg; 
} 

可能是值得一試。或者,當然,只需在適當的#pragma下將此警告消除。