使用printf時,它們具有相同的格式說明符,但使用scanf時,它們沒有相同的格式說明符。
為什麼是這樣?因為printf的參數被提升,而scanf的參數(作為指針)卻不被提升。
這種論點提升到底是什麼?當較小尺寸的參數(特別是char,short和float)傳遞給可變參數函數(如printf之類的函數,其參數數量不固定)時,它們將轉換為較大尺寸。Char和short轉換為int,float轉換為double。
為什麼這樣 據我所知,純粹出於歷史原因。C的設計師認為這是個好主意,因為這些轉換基本上是免費的,因為所有類型的尺寸都足夠小,可以放入單個寄存器或堆棧中的單個「單元」(將內容壓入堆棧必須與某些字節邊界對齊,例如,堆棧上的每個項目都必須以4的倍數的地址開頭。同樣,顯然,這種轉換減少了傳遞參數時的錯誤。
因此,當您向printf傳遞float類型的參數時,實際上它會在轉換為printf之前就轉換為double類型。我們可以使用調試器證明這一點。我寫了下面的C代碼:
主要功能編譯成:
我在調用printf之前(在地址0x63b處)放了一個斷點。根據Linux x86_64調用約定,浮點參數在XMM寄存器中傳遞(CPU中特殊的小內存位置,可用於對多條數據並行執行同一條指令,但實際上可用於大多數事情) 。因此,我查看了第一個XMM寄存器xmm0,然後:
xmm0中的值之一,當解釋為雙精度值時,是1,恰好是我們傳遞給printf的值。同時,當將該寄存器中的值解釋為浮點數時,它們是這樣(巧合的是,我們得到1.875)。因此,轉換確實發生了。這就解釋了為什麼對於printf,我們在浮點數和雙精度參數中都使用%f -浮點數無論如何都會轉換為雙精度,因此printf不能分辨出兩者之間的區別。
同時,scanf的參數是指針,因此不受此轉換的限制。其原因是因為所有指針類型僅包含內存地址,並且所有內存地址都具有相同的大小(在我的64位計算機上為64位)。因此,當scanf在其格式字符串中獲得%f時,它將期望一個float *類型的指針,而當它獲得%lf時,將期望得到double *類型的指針。如果格式說明符和指針的類型不匹配,則會產生一些有趣的結果。
由於float的大小為4個字節,而double的大小為8個字節(至少在我的機器上),因此當我們寫入float *類型的指針所指向的位置時,我們將覆蓋4個字節的內存。同時,如果我們寫入由雙*指向的位置,則將覆蓋8個字節的內存。考慮以下代碼:
printf說明符上的.15標誌只是使printf精確度更高。由於我們使用的是雙精度值(並且正如我剛剛說的那樣,%f也適用於printf的雙精度值),因此我們實際上可以訪問具有這種精度的數字(只要它們不是太大而不能填充即可。我實際上不是對浮點表示非常了解)
編譯並運行後,結果如下:
如您所見,如果我們忽略所有類型的fuckery,則數字應該匹配,但它們甚至不相近。這是為什麼?
好吧,我們給scanf%f說明符,所以它期望一個浮點數*。但是我們傳遞了一個雙*。現在,這些指針的實際值都只是地址-scanf不知道它們之間的區別。它進行了下去,讀取我們輸入的值,並將其存儲為float 。但是浮點數僅佔用4個字節,因此scanf只會在我們可用的double變量的8個字節中寫入4個字節。在我的情況下,由於我的機器是低位字節序的,因此似乎對應於double變量的細粒度(小有效位)數字的第4個字節將被覆蓋。因此,當我們列印出double double back時,我們得到的數字幾乎與以前相同,但最低有效數字有所變化。#c語言#