[英]Ternary operator in C
為什么該程序給出了意外的數字(例如: 2040866504
, -786655336
)?
#include <stdio.h>
int main()
{
int test = 0;
float fvalue = 3.111f;
printf("%d", test? fvalue : 0);
return 0;
}
為什么打印出意外的數字而不是0
? 它應該進行隱式類型轉換嗎? 該程序用於學習目的,沒什么大不了的。
您的平台最有可能在浮點寄存器中傳遞浮點值,並在其他寄存器(或堆棧)中傳遞整數值。 您告訴printf
尋找一個整數,因此它正在尋找寄存器中傳入的整數(或在堆棧上)。 但是您將其傳遞給float
,因此零被放置在printf
從未看過的浮點寄存器中。
三元運算符遵循語言規則來確定其結果的類型。 有時不能是整數,有時不能是浮點數。 這些可能是不同的大小,存儲在不同的地方,依此類推,這使得不可能生成合理的代碼來處理兩種可能的結果類型。
這是一個猜測。 也許正在發生完全不同的事情。 未定義的行為是未定義的,這是有原因的。 如果沒有大量平台和編譯器細節的經驗和知識,這類事情可能是無法預測的,並且很難理解。 永遠不要讓某人說服UB正常或安全,因為它似乎可以在他們的系統上運行。
因為您使用%d
來打印float
值。 使用%f
。 使用%d
打印float
值會調用未定義的行為 。
編輯:關於OP的評論;
為什么打印隨機數而不是0?
編譯此代碼時,編譯器應警告您:
[Warning] format '%d' expects argument of type 'int', but argument 2 has type 'double' [-Wformat]
此警告不言自明,這行代碼正在調用未定義的行為。 這是因為轉換說明%d
指定printf
要將int
值從二進制轉換為十進制數字的字符串,而%f
對float
值執行相同的操作。 通過傳遞fvalue
編譯器后,知道它是float
型的,但是另一方面,它會看到printf
需要一個int
類型的參數。 在這種情況下, 有時會達到您的期望,有時會達到我的期望。 有時,它可以實現沒人期望的事情 ( David Schwartz的精彩評論)。
參見測試案例1和2 。 %f
可以正常工作。
它應該進行隱式類型轉換嗎?
沒有。
盡管現有的贊成答案是正確的,但我認為它們過於技術性,並且忽略了初學者程序員可能具有的邏輯:
讓我們看一下引起某些頭腦混亂的語句:
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
表示float
, 0
表示整數。 但是,他了解到三元運算符的不同可能結果必須是同一類型! int
和float
不是。 在這種情況下,“ float
獲勝”, 0
變為0.0f
!
現在printf
不是安全的類型。 這意味着您可以錯誤地說“給我打印一個整數”,並在不通知編譯器的情況下傳遞浮點數。 確實發生了。 不管test
的值是什么,編譯器都會得出結果將是float
類型。 因此,您的代碼等效於:
float x = 0.0f;
printf("%d", x);
此時,您會遇到不確定的行為。 float
根本就不是integral
什么是預期%d
。
觀察到的行為取決於您使用的編譯器和計算機。 您可能會看到跳舞的大象,盡管大多數航站樓都不支持該afaik。
什么時候有表達式E1 ? E2 : E3
E1 ? E2 : E3
,涉及四種類型。 表達式E1
, E2
和E3
都有一個類型(並且E2
和E3
的類型可以不同)。 此外,整個表達式E1 ? E2 : E3
E1 ? E2 : E3
具有類型。
如果E2
和E3
具有相同的類型,那么這很容易:整個表達式具有該類型。 我們可以用如下的元符號表示:
(T1 ? T2 : T2) -> T2
"The type of a ternary expression whose alterantives are both of the same type T2
is just T2."
如果它們的類型不同,則事情會變得有些有趣,並且情況與E2
和E3
一起參與算術運算非常相似。 例如,如果將int
和float
一起加,則int
操作數將轉換為float
。 那就是程序中正在發生的事情。 類型情況是:
(int ? float : int) -> float
測試失敗,因此int
值0
轉換為float
值0.0
。
此float
值與printf
的%d
轉換說明符不兼容,后者需要int
。
更准確地說, float
值再經歷一次。 當float
作為尾隨參數之一傳遞給可變參數函數時,它將轉換為double
。
因此,實際上將double
值0.0傳遞給了期望為int
printf
。
在任何情況下,它都是未定義的行為:它是不可移植的代碼,C語言的ISO標准定義不提供其含義。
從這里開始,我們可以應用特定於平台的推理,為什么我們不僅僅看到0
。 假設int
是32位,四字節類型,而double
是常見的64位,8字節IEE754表示形式,並且全零位用於0.0
。 那么,為什么printf
不會將此全零位的32位部分視為int
值0
呢?
64位double
變量值很有可能在將其放到堆棧上時強制8個字節對齊,可能會將堆棧指針移動4個字節。 然后printf
從這四個字節中取出垃圾,而不是從double
值中取出零位。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.