2013-09-28 167 views
13

爲什麼這個程序給出了意想不到的數字(例如:2040866504,-786655336)?三元運算符C

#include <stdio.h> 
int main() 
{ 
    int test = 0; 
    float fvalue = 3.111f; 
    printf("%d", test? fvalue : 0); 

    return 0; 
} 

爲什麼打印意外的數字而不是0?它應該做隱含的類型轉換嗎?這個程序對於學習目的來說並不嚴重。

+0

你是什麼意思的「隨機」?你試圖將一個浮點數作爲一個整數來打印,但是這個數字應該是'0',它們在兩種類型中都有相同的位表示。 –

+1

隨機,或只是不正確? –

+0

這個問題似乎是脫離主題,因爲它是關於誰知道什麼。 – 2013-09-28 05:08:33

回答

18

最有可能的,平臺通行證在一個不同的寄存器浮點值的浮點寄存器和整數值(或在堆棧上)。你告訴printf尋找一個整數,所以它看着寄存器整數傳入(或在堆棧中)。但是你通過了一個float,所以零被放置在printf永遠不會看到的浮點寄存器中。

三元運算符遵循語言規則來決定其結果的類型。它有時不能是一個整數,有時候是一個浮點數。這些可能是不同的大小,存儲在不同的地方等等,這將使得不可能生成合理的代碼來處理可能的結果類型。

這是一個猜測。也許完全不同的事情正在發生。出於某種原因未定義的行爲未定義。如果沒有大量關於平臺和編譯器細節的經驗和知識,這些類型的東西可能無法預測,也很難理解。永遠不要讓別人說服你,因爲UB似乎在他們的系統上工作,所以UB沒問題或者安全。

+0

這個答案中唯一缺少的東西就是爲什麼參數是浮點數。 –

+0

我在中段添加了更多關於這方面的信息。 –

+0

哇...很好的解釋!現在這個概念很清楚:-) – Tonmoy

11

因爲您正在使用%d來打印float值。使用%f。使用%d打印float值會調用未定義的行爲。編輯: 關於OP的評論;

爲什麼打印隨機數而不是0?

當你編譯這段代碼,編譯器應該給你一個警告:

[Warning] format '%d' expects argument of type 'int', but argument 2 has type 'double' [-Wformat] 

此警告的是,這行代碼調用一個未定義的行爲的自我解釋。這是因爲,轉換規範%d指定printfint值從二進制轉換爲十進制數字的字符串,而%ffloat值的做法相同。通過fvalue編譯器知道它是float類型,但另一方面它看到printf需要int類型的參數。在這種情況下,有時會達到您的預期,有時會達到我的預期。有時它沒有人期望(由David Schwartz尼斯評論)。
查看測試案例12。它與%f工作正常。

它應該做隱式類型轉換嗎?

+0

的確如此,但我認爲這與此處顯示的特定代碼無關。 –

+0

爲什麼打印隨機數而不是0?它應該做隱含的類型轉換嗎? – Tonmoy

+4

@MarkRansom;爲什麼?請參閱測試用例[1](http://ideone.com/Ig5uub)和[2](http://ideone.com/woe4XU)。 – haccks

8

雖然現有upvoted答案是正確的,我覺得他們都太技術而忽略邏輯初學者程序員可能有:

讓我們來看看本聲明的一些首腦造成混亂:

printf("%d", test? fvalue : 0); 
     ^^ ^ ^
     | |  |  | 
     | |  |  - the value we expect, an integral constant, hooray! 
     | |  - a float value, this won't be printed as the test doesn't evaluate to true 
     | - an integral value of 0, will evaluate to false 
     - We will print an integer! 

編譯器看到的有點不同。他同意test的含義false的值。他同意fvalue是一個整數float0。但是,他了解到三元運算符的不同可能結果必須是相同的類型! intfloat都沒有。在這種情況下,「float獲勝」,0變成0.0f

現在printf是不安全的。這意味着你可以錯誤地說「打印一個整數」並傳遞一個浮點數,而不用編譯器注意。確實發生了。無論test的值是多少,編譯器都會推斷結果將是float。因此,您的代碼相當於:

float x = 0.0f; 
printf("%d", x); 

此時,您遇到未定義的行爲。 float根本不是什麼integral%d預計什麼。

觀察到的行爲取決於您正在使用的編譯器和機器。你可能會看到跳舞的大象,儘管大多數終端不支持afaik。

1

當我們有表達式E1 ? E2 : E3時,涉及四種類型。表達式E1E2E3每個都有一個類型(並且E2E3的類型可以不同)。此外,整個表達式E1 ? E2 : E3有一個類型。

如果E2E3具有相同的類型,則很容易:整體表達式具有該類型。

(T1 ? T2 : T2) -> T2 
"The type of a ternary expression whose alterantives are both of the same type T2 
is just T2." 

如果它們不具有相同的類型,事情變得有些有趣,而且情況頗爲相似E2E3被捲入一起算術:我們可以通過元標記這樣表達這種操作。例如,如果您將intfloat加在一起,那麼int操作數將轉換爲float。這就是你的程序中發生的事情。類型的情況是:

(int ? float : int) -> float 

測試失敗,並且因此int0轉換爲float0.0

float值與%d轉換說明符printf不兼容,這需要int

更確切地說,float值經歷了一次。當float作爲可變參數函數的尾隨參數之一傳遞時,它將轉換爲double

所以實際上double值0.0正在傳遞到printf它期望int

在任何情況下,它都是未定義的行爲:它是不可移植的代碼,C語言的ISO標準定義不提供含義。

從這裏開始,我們可以應用特定於平臺的推理,爲什麼我們不會看到0。假設int是32位,4字節類型,並且double是常見的64位,8字節,IEE754表示,並且全位零用於0.0那麼,爲什麼不是printf作爲int0處理的全零位的32位部分?

很可能,64位double參數值在放入堆棧時強制8字節對齊,可能會將堆棧指針移動四個字節。然後printf從這四個字節中取出垃圾,而不是從double值的零位。