2010-03-08 88 views
10

考慮這個計劃如何在printf中處理float/double轉換爲int?

int main() 
{ 
     float f = 11.22; 
     double d = 44.55; 
     int i,j; 

     i = f;   //cast float to int 
     j = d;   //cast double to int 

     printf("i = %d, j = %d, f = %d, d = %d", i,j,f,d); 
     //This prints the following: 
     // i = 11, j = 44, f = -536870912, d = 1076261027 

     return 0; 
} 

有人可以解釋爲什麼從雙/浮到INT鑄造正常工作在第一種情況下,和在printf中完成時不起作用?
該程序是在32位Linux機器上的gcc-4.1.2上編譯的。


編輯: Zach's answer似乎是合乎邏輯的,即使用格式說明,以弄清楚如何彈出堆棧。然後考慮這個後續問題:

int main() 
{ 

    char c = 'd'; // sizeof c is 1, however sizeof character literal 
        // 'd' is equal to sizeof(int) in ANSI C 

    printf("lit = %c, lit = %d , c = %c, c = %d", 'd', 'd', c, c); 
    //this prints: lit = d, lit = 100 , c = d, c = 100 
    //how does printf here pop off the right number of bytes even when 
    //the size represented by format specifiers doesn't actually match 
    //the size of the passed arguments(char(1 byte) & char_literal(4 bytes))  

return 0; 
} 

這是如何工作的?

+0

我有類似的疑問。看到這個線程:http://stackoverflow.com/questions/2377733/how-does-this-program-work – Lazer 2010-03-08 03:16:14

+1

字符是一個單一的字符 - 它只是一個8位整數。當你對整型小於int的任何類型的操作時,它們會被提升爲整數。這包括調用函數時。所以實際上它不是隨機的機會導致你的printf調用工作,這種行爲是定義的。 實際上,在大多數C ABI中,您始終爲堆棧上傳遞的每個變量分配至少一個機器字。 – 2016-01-22 16:00:04

+0

@SurajJain是,''參數傳遞只考慮根據默認參數促銷推廣的類型(C11§6.5.2.2/ 6,§7.16.1.1/ 2),它確保'char'和'int'兼容。然而,這與用於算術的促銷不完全相同。另外,就ABI而言,推斷語言是很危險的。確認它確實需要檢查規則。 – Potatoswatter 2016-08-23 05:04:26

回答

19

printf函數使用格式說明符來確定彈出堆棧的內容。所以當它看到%d時,它彈出4個字節並將它們解釋爲int,這是錯誤的((float)3.0的二進制表示與(int)3不同)。

您需要使用%f格式說明符或將參數轉換爲int。如果您使用的gcc一個新的足夠的版本,那麼強烈的警告打開捕獲這種錯誤:

$ gcc -Wall -Werror test.c 
cc1: warnings being treated as errors 
test.c: In function ‘main’: 
test.c:10: error: implicit declaration of function ‘printf’ 
test.c:10: error: incompatible implicit declaration of built-in function ‘printf’ 
test.c:10: error: format ‘%d’ expects type ‘int’, but argument 4 has type ‘double’ 
test.c:10: error: format ‘%d’ expects type ‘int’, but argument 5 has type ‘double’ 

回答這個問題的編輯部分:

C'S整數提升規則說,所有的當作爲可變參數傳遞時,小於int的類型被升級爲int。所以在你的情況下,'d'被升級到int,然後printf彈出一個int並鑄造爲char。我能找到的最佳參考是this blog entry

+3

+1我喜歡強烈的警告。 – 2010-03-08 01:50:06

+0

我有一個後續問題,請參閱我的問題的編輯部分。如果問題不明確,請發表評論...謝謝 – Sandip 2010-03-08 02:45:37

+1

當參數列表的變量部分傳遞時,'char'參數被提升爲'int'的確是這樣(這是在'c'傳入例如),但是字符常量常量(如''d'')已經是'int'類型。 (注意在理論上,在某些編譯器中,可以將'char'參數提升爲'unsigned int')。 – caf 2010-03-08 04:21:44

2

因爲您不使用浮動格式說明,嘗試用:

printf("i = %d, j = %d, f = %f, d = %f", i,j,f,d); 

否則,如果你想4個整數,你必須傳遞參數給printf之前丟掉。

printf("i = %d, j = %d, f = %d, d = %d", i,j,(int)f,(int)d); 
0

printf使用變長參數列表,這意味着您需要提供類型信息。你提供的信息是錯誤的,所以會讓你感到困惑。 Jack提供了實用的解決方案。

4

Jack's answer說明如何解決您的問題。我要解釋爲什麼你會得到意想不到的結果。您的代碼就相當於:

float f = 11.22; 
double d = 44.55; 
int i,j,k,l; 

i = (int) f; 
j = (int) d; 
k = *(int *) &f;   //cast float to int 
l = *(int *) &d;   //cast double to int 

printf("i = %d, j = %d, f = %d, d = %d", i,j,k,l); 

的原因是fd傳遞給printf的值,然後這些值被解釋爲int秒。這不會更改二進制值,因此顯示的數字是floatdouble的二進制表示形式。在生成的組件中,從floatint的實際演員要複雜得多。

+2

看到我的答案可能會幫助他理解他的輸出,但代碼是不相同的。你不能爲錯誤解釋printf或任何可變參數函數編寫等價代碼,同時仍然調用可變參數函數。 – 2010-03-08 01:59:19

+0

我的代碼確實(對我來說,也許對他來說)他的代碼在做什麼(對他來說)。兩者都是未定義的行爲,所以任何人都不能保證特別做任何事情。 – 2010-03-08 02:27:16

7

有因爲沒有這樣的事情「鑄造intprintf」。 printf不會做,也不能做任何演員。不一致的格式說明符導致未定義的行爲。

實踐中printf只是接收原始數據而重新解釋它作爲格式說明符所暗示的類型。如果您通過double值並指定int格式說明符(如%d),則printf將採用該值double,並盲目地將其重新解釋爲int。結果將是完全不可預測的(這就是爲什麼在C中正式導致未定義的行爲)。

+0

當然可以施展 - 爲什麼不呢?所有的信息都在那裏。編譯器比我們更瞭解參數類型(是否有人提到默認促銷?),並且轉換說明符是標準的一部分。它沒有正確轉換的原因部分是歷史性的(原來的C編譯器對於這種分析過於簡單),現在改變甚至可能破壞依賴於重新解釋的代碼。 – 2016-02-23 17:57:08

+0

@彼得答:施耐德:真的嗎?格式字符串是運行時值的情況怎麼樣?即使「轉換說明符是標準的一部分」,您如何期望編譯器「投射」任何東西?更一般地說,這是C語言設計的一個基本原則:它的標準庫不依賴於編譯器的「神奇」功能(可能只有少數例外)。實際上,每個庫功能都可以由用戶使用C語言本身來實現。只要C語言遵循這個基本原則,'printf'不會被任何編譯器魔術所「增強」。 – AnT 2016-02-23 18:18:13

+0

不可否認,我沒有想到運行時格式化字符串(因爲在所有的現實中:你最後一次使用它的時間是?)。但標準可以區分這些情況 - 看看他們對「sizeof」做了什麼。至於演員:很明顯,編譯器知道如何投射類型,所以我無法在這裏檢測到任何額外的魔法;而且我也認爲演員可能可以用C來實現。就像一個大的if/else如果切換轉換運算符,然後轉換適當的強制轉換。突破變化寧願成爲省略號原型的變化語義。 – 2016-02-24 08:30:31

1

您的後續代碼工作的原因是因爲它被壓入堆棧之前的字符常量被提升到一個int。所以printf會彈出4個字節的%c和%d。實際上,字符常量的類型是int,而不是char類型。 C很奇怪。

0

值得注意的是printf,是與可變長度的參數列表的功能,從不接收浮子;浮動論據是「老派」晉升爲雙打。

最近的一項標準草案首先介紹了 「老派」 默認促銷(n1570,6.5.2.2/6):

If the expression that denotes the called function has a type that does not include a prototype, the integer promotions are performed on each argument, and arguments that have type float are promoted to double. These are called the default argument promotions.

然後討論可變參數列表(6.5.2.2/7):

The ellipsis notation in a function prototype declarator causes argument type conversion to stop after the last declared parameter. The default argument promotions are performed on trailing arguments.

printf的後果是,它是不可能的「打印」一個真正的浮動。 float表達式總是被提升爲double,這是IEEE 754實現的8字節值。此促銷發生在主叫方;開始執行時,printf在堆棧上已經有一個8字節的參數。

如果我們將11.22分配給double並檢查其內容,使用我的x86_64-pc-cygwin gcc,我看到字節序列000000e0a3702640。

這解釋了由printf打印的int值:該目標上的Ints仍然有4個字節,因此只有前四個字節0​​00000e0被評估,並且再次以小端排序,即爲0xe0000000。這是十進制的-536870912。

如果我們反向所有8個字節的,因爲英特爾處理器將在小尾數雙打,我們也得到402670a3e0000000。我們可以檢查這個字節序列在IEEE格式on this web site中表示的值;接近於1.122E1,即預期的結果是11.22。