標籤:

如果scanf的格式指示符是%f,賦給一個double型的變數,在內存層面上會發生什麼?


C語言不知道什麼是內存

如果你想問常見實現,那麼一個float類型的對象會被強行覆蓋到一個8位元組空間的高4位元組空間,具體覆蓋的是尾數還是指數,取決於系統是大端序還是小端序

我維護的在線C 語言開發環境,最新版的scanf對於不匹配的格式參數直接報錯,不允許這種錯誤出現,在此之前%f和%lf都是按double做的,你傳float*還是double*都沒區別

反正標準沒說錯了會怎麼樣,所以我怎麼寫都是沒錯的。


嚴格按照C語言標準(第7.21.6.2節,第10段,fscanf)來說:

..... If this object does not have an appropriate type, or if the result of the conversion cannot be represented in the object, the behavior is undefined.

這是一個未定義行為,於是具體實現就取決於各家編譯器了,一個常見的實現是:

根據C/C++可變參數的原理,調用含有可變個參數的函數時,調用者在傳遞參數時只是把值放到了棧上,並沒有把類型信息也同時傳遞過去,所以scanf這樣的函數必須額外地通過格式指示符來傳遞類型信息。scanf內部也只能『就當調用者傳遞了正確的信息』來執行。

在題主的問題中,調用者傳遞了一個double型變數d的首地址p。本來如果是%lf的話,scanf會根據用戶輸入來構造一個sizeof(double)=8位元組的double變數,並把這個變數複製到『以p所指向的地址開始的8個位元組』上面去,也就是正好填滿了變數d。那麼類似的,現在用了%f,scanf則會構造一個float並複製到『以p所指向的地址開始的sizeof(float)=4個位元組』上面去,也就是填上了『變數d的內存空間的前一半』,而另外4個位元組就保持原樣不變了。

至於這個8個位元組『如果實際上按照double的語義來解析的話會得到個什麼玩意兒』,就要按照IEEE754(浮點數標準)來執行了,跟一般的double沒有區別。


%f匹配float*,%lf匹配double*,按這個規定,你的代碼就是ok的,否則,出什麼事並不保證,因為是undefined behavior,可能貌似正常運行,也可能報錯,也可能出其他事情

至於內存什麼的,那是實現層的事情,語言並不怎麼關心,至少這個問題上不涉及


這個問題比較有趣。

我們先貼一段 scanf 對於浮點處理的源碼(來自scanf.c,minix內核源碼(引用自cnblogs))

這裡是對浮點值的存儲位置

#ifndef NOFLOAT
long double ld_val;
#endif

默認將所有浮點型用 Lf 來存儲,可以保證所有精度。

這裡是對浮點數的讀入處理

#ifndef NOFLOAT
case "e":
case "E":
case "f":
case "g":
case "G":
if (!(flags FL_WIDTHSPEC) || width &> NUMLEN)
width = NUMLEN;

if (!width) return done;
str = f_collect(ic, stream, width);

if (str &< inp_buf || (str == inp_buf (*str == "-" || *str == "+"))) return done; /* * Although the length of the number is str-inp_buf+1 * we don"t add the 1 since we counted it already */ nrchars += str - inp_buf; if (!(flags FL_NOASSIGN)) { ld_val = strtod(inp_buf, tmp_string); if (flags FL_LONGDOUBLE) *va_arg(ap, long double *) = (long double) ld_val; else if (flags FL_LONG) *va_arg(ap, double *) = (double) ld_val; else *va_arg(ap, float *) = (float) ld_val; } break; #endif

我們可以發現,賦值是由最後一個 if 實現的。

例如 *va_arg(ap, float *) = (float) ld_val; 是對浮點型的賦值。

因為地址在同PC上大小固定,所以實際上賦值左元 *va_arg(ap, float *) 、 *va_arg(ap, double *) 、和 *va_arg(ap, long double *) 沒有任何區別,僅有的區別就是賦值右元的強制轉換過程。

因為有 FLT_MAX 3.402823466e+38F 和 DBL_MAX 1.7976931348623158e+308 ,所以我們可以知道 float 的精度要遠低於 double,而強轉可能會損失精度。

而 float 的內存是這樣的:

因此我們可以知道:

如果scanf的格式指示符是%f,賦給一個double型的變數:

當輸入值在 float 表示範圍內且為非負時,不會有任何差別。

當輸入值在 float 表示範圍外且為非負時,會損失變數的 0xFFFFFFFF00000000 中的所有非零位的內容;

當輸入值為負值時。。。自求多福吧,float的符號位在 double 裡面是尾數位的區段,會讓數據變得很詭譎,而且,0xFFFFFFFF00000000 的所有內容也全部丟失,也就是保持原樣。


推薦閱讀:

指針可以修改const修飾的變數么?
int (*pf)(1024)為什麼是函數調用?
為什麼 Windows API 使用 stdcall 調用約定?
c++中的字元串常量為什麼可以賦值給char*?
我想自己用C/C++做一個腳本語言解釋器,但是不知道需要什麼知識?

TAG:C編程語言 | CC |