[英]Why does printf print random value with float and integer format specifier
[英]Why wasn't a specifier for `float` defined in `printf`?
看起來應該是這樣,至少可以在int
中使用(至少在C99中)長度修飾符: %hhd
, %hd
, %ld
和%lld
表示帶signed char
, short
, long
和long long
。 甚至還有一個適用於double
的長度修飾符: %Lf
表示long double
。
問題是為什么他們省略float
? 按照該模式,可能是%hf
。
因為在C可變參數函數調用中,任何float
參數都被提升為double
(即轉換為double
,所以printf
獲得了double
,並將使用va_arg(arglist, double)
將其放入其實現中。
在過去(C89和K&R C),每個float
參數都轉換為double
。 當前標准省略了對具有明確原型的固定Arity函數的推廣。 它與實現的ABI和調用約定有關(並在中進行了詳細說明)。 實際上,將float
值作為參數傳遞時通常會加載到雙浮點寄存器中,但是細節可能會有所不同。 閱讀Linux x86-64 ABI規范作為示例。
另外,沒有實際的理由為float
提供特定的格式控制字符串,因為您可以根據需要調整輸出的寬度(例如,使用%8.5f
),並且%hd
在scanf
比在中更有用(幾乎是必需的)。 printf
除此之外, 我想,之所以 (省略%hf
指定float
促進的以double
於呼叫者在printf
) 是歷史的 :首先,C是一個系統的編程語言,而不是一個一個HPC(Fortran的是優選的,HPC也許直到1990年代后期)和float
並不是很重要; 它曾經(現在仍然)被認為是short
,一種降低內存消耗的方法。 而且,當今的FPU足夠快(在台式機或服務器計算機上)可以避免使用float
除非這樣做是為了減少內存。 基本上,您應該相信每個float
都在某個位置(可能在 FPU或CPU 內部 )轉換為double
。
實際上,您的問題可能會被解釋為:為什么%hd
對於printf
存在(它基本上是無用的,因為當您將short
傳遞給它時, printf
會得到一個int
;但是scanf
需要它!)。 我不知道為什么,但是我想比在系統編程中更有用。
您可能需要花一些時間游說下一個ISO C標准,以使printf
接受%hf
進行float
(在printf
調用時提升為double
,例如short
-s提升為int
),並且在double precision值超出范圍時會發生不確定的行為float
-s,並且scanf
對稱地%hf
接受float
指針。 祝你好運。
由於默認參數提升 。
printf()
是一個可變參數函數( ...
在其簽名),所有float
參數都提升到double
。
C11§6.5.2.2函數調用
6如果表示被調用函數的表達式的類型不包含原型,則對每個自變量執行整數提升,並將具有
float
類型的自變量提升為double
。 這些稱為默認參數提升。7函數原型聲明器中的省略號引起參數類型轉換在最后聲明的參數之后停止。 默認參數提升是對尾隨參數執行的。
由於調用可變參數函數時默認的參數提升,因此在函數調用之前將float
值隱式轉換為double
,並且無法將float
值傳遞給printf
。 由於無法將float
值傳遞給printf
,因此無需為float
值使用顯式格式說明符。
話雖如此, AntoineL在評論中提出了一個有趣的觀點,即%lf
(當前在scanf
用於對應於實參類型double *
)可能曾經代表“ long float
”,這在C89之前的日子中是類型的同義詞,根據C99原理第42頁。 按照這種邏輯, %f
旨在代表已轉換為double
的float
值可能是有道理的。
關於hh
和h
長度修飾符, %hhu
和%hu
為這些格式說明符提供了一個定義明確的用例:您可以打印大的unsigned int
或unsigned short
的最低有效字節,而無需強制轉換,例如:
printf("%hhu\n", UINT_MAX); // This will print (unsigned char) UINT_MAX
printf("%hu\n", UINT_MAX); // This will print (unsigned short) UINT_MAX
從int
到char
或short
的縮小轉換將不會導致什么特別明確的定義,但至少是實現定義的 ,這意味着需要實現才能實際記錄此決定。
按照該模式,應該是
%hf
。
按照您觀察到的模式, %hf
應該將超出float
范圍的值轉換回float
。 但是,從double
到float
這種狹窄轉換會導致未定義的行為 ,並且沒有unsigned float
這樣的事情。 您看到的模式沒有意義。
為了形式正確, %lf
並不表示一個long double
精度參數,如果要傳遞一個long double
精度參數,則將調用未定義的行為 。 從文檔中可以明顯看出:
l
(ell)...對后面的a
,A
,e
,E
,f
,F
,g
或G
轉換說明符沒有影響。
我很驚訝沒有其他人對此有所了解? 就像%f
一樣, %lf
表示一個double
參數。 如果要打印long double
,請使用%Lf
(大寫字母ell)。
此后應該有意義的是, printf
和scanf
%lf
都對應於double
和double *
參數... %f
之所以例外,僅是因為默認的參數提升,原因如前所述。
...和%Ld
也不意味着long
。 這意味着未定義的行為 。
根據ISO C11標准6.5.2.2 Function calls /6
和/7
,在表達式的上下文中討論函數調用(我的重點是):
6 /如果表示被調用函數的表達式的類型不包括原型,則對每個參數執行整數提升,並且將類型為float的參數提升為雙精度。 這些稱為默認參數提升。
7 /如果表示被調用函數的表達式的類型確實包括原型,則將參數隱式轉換為相應參數的類型,就像通過賦值一樣,將每個參數的類型視為的非限定版本。其聲明的類型。 函數原型聲明器中的省略號引起參數類型轉換在最后聲明的參數之后停止。 默認參數提升是對尾隨參數執行的。
這意味着原型中...
之后的所有float
參數都將轉換為double
,並且printf
調用族是以這種方式定義的( 7.21.6.11
等):
int fprintf(FILE * restrict stream, const char * restrict format, ...);
因此,由於無法通過printf()
family調用實際接收浮點數,因此為其指定特殊格式說明符(或修飾符)幾乎沒有意義。
考慮到scanf
具有用於float,double或long double的單獨格式說明符,我不明白為什么printf
和類似函數沒有以類似方式實現,但這就是C / C ++和標准的結局。
推送或彈出操作的最小大小可能會出現問題,具體取決於處理器和當前模式,但這可以通過默認填充來解決,這類似於局部變量或結構中變量的默認對齊方式。 當Microsoft從16位編譯器變為32/64位編譯器時,Microsoft放棄了對80位(10字節) long double
的支持,現在將long double
s與double
s(64位/ 8字節)相同。 他們可以根據需要將其填充到12或16個字節的邊界,但這並未完成。
閱讀fscanf下面的C基本原理,可以找到以下內容:
C99的新功能:在C99中添加了hh和ll長度修飾符。 ll支持新的long long int類型。 hh增加了將字符類型與所有其他整數類型相同的功能; 這對於實現SCNd8之類的宏很有用(請參閱7.18)。
因此,應該添加hh
是為了為所有新的stdint.h
類型提供支持。 這可以解釋為什么為小整數而不是小浮點添加了長度修飾符。
它並不能解釋為什么C90不一致地具有h
但沒有hh
。 C90中指定的語言並不總是一致的,就這么簡單。 后來的版本繼承了不一致的地方。
當發明C時,所有浮點值在用於計算或傳遞給函數(包括printf
)之前都已轉換為通用類型(即double
),因此printf
無需在浮點類型之間進行任何區分。
為了提高算術效率和准確性,IEEE-754浮點標准定義了一個80位類型,該類型比普通的64位double
但可以更快地處理。 目的是給定類似a=b+c+d;
的表達式a=b+c+d;
與將總和(b+c)
計算為64相比,將所有內容轉換為80位類型,將三個80位數字加在一起並將結果轉換為64位類型既更快又更准確。位類型,然后將其添加到d
。
為了支持新類型,ANSI C定義了一個新類型的long double
,其實現可以引用新的80位類型或64位double
。 不幸的是,盡管IEEE-754 80位型的目的是自動擁有的所有值提升到新的類型,他們已經被提拔的方式double
,ANSI說得那么新類型被傳遞給printf
或其他可變參數的方法與其他浮點類型不同,因此使這種自動升級變得難以為繼。
因此,創建C時存在的兩種浮點類型都可以使用相同的%f
格式說明符,但是之后創建的long double
要求使用不同的%Lf
格式說明符( 大寫的 L
)。
%hhd
, %hd
, %ld
和%lld
已添加到printf
以使格式字符串與scanf
更加一致,盡管由於默認參數提升它們對於printf
來說是多余的。
那么,為什么不%hf
添加float
? 這很容易:查看scanf
的行為, float
已經具有格式說明符。 是%f
。 並且double
的格式說明符為%lf
。
該%lf
正是C99添加到printf
。 在C99之前, %lf
的行為是不確定的(通過省略標准中的任何定義)。 從C99開始,它是%f
的同義詞。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.