C中printf與scanf函數讀取與儲存參數實現辦法

我是C語言的初學者,學到printf和scanf時有一些疑惑,於是自己研究總結了一些東西,可能會有錯誤,所以希望各位大牛指正哦~\(≧▽≦)/~啦啦啦

===============================================

I   關於Printf向堆棧傳參方式及儲存讀取方式

一、printf的格式爲:

printf(“格式字符串”,參數1,參數2,……);

其傳遞原理是,先將參數1、參數2、…傳遞到計算機堆棧中,堆棧類似於一個寄存器。計算機根據變量類型(Datatype)去儲存而非根據格式說明符(Format specifier)去儲存這些數據。之後printf根據格式說明符的形式去從堆棧中讀取這些數據而非根據變量類型。所以一旦變量類型與格式說明符不匹配就會出現錯誤。

二、考察一下代碼

 

輸出結果如下所示

 

甲行輸出的n1n2並沒有問題,而n3n4出現異常,這是因爲n3n4是長整形變量而使用%e的浮點指數格式符非法輸出所導致。同理,乙行的四個參量輸出均出現異常,這是因爲當第一個浮點型變量用%ld格式符輸出出現異常時導致後面都出現異常。

三、原因深層探究

首先根據1中所說,考察printf如何向堆棧傳參。首先先將四個參數都變成二進制數,爲了方便起見,統一以十六進制表示,兩個十六進制數代表一個字節。需要指出,浮點型變量是以IEEE754標準儲存(即包括符號位、階碼、尾數),另外,單精度浮點型float自動轉換爲雙精度浮點型double(64位,包括1個符號位、11個階碼位以及52位尾數位)


向堆棧傳參的原則是:每傳遞一個參量,事先開闢一個與之匹配的大小的空間;由低字節位向高字節位依次儲存;對於一個數,其最右位低字節,最左位高字節;所以n1n2n3n4儲存方式爲

 

如圖所示,n1~n4分別從低字節位向高字節位儲存,在儲存64位的double型的n1float自動轉換)的時候,先開闢64位連續空間,從低字節向高字節儲存,其讀取方式則是從下到上蛇形讀取。後面同理。

值得注意的是,當printf連續儲存參量時,開闢的空間不一定是連續的。

在甲行中,printf%.1e格式符開始讀取時,它會一次性按照儲存順序讀取8字節,所以n1n2輸出正常。但是當掃描至n3時,由於n3只有4字節,所以會將n4的四個字節數據也讀取,即一次性劃出8字節的範圍,從下至上蛇形讀取:

 

此時n3的讀取結果如圖所示,而這個數據被用%.1e讀取時,會被認爲是浮點型,所以儘管n3long型,但是用IEEE754標準下的double規則翻譯爲十進制就是3.1e+46

繼續讀取第四個%.1e,由於n4的位置已被讀取,所以繼續讀取時會隨機讀取8個字節,這些內存的數據將是未知的。從而n4的結果也是未知的。

 

乙行按照%ld讀取,%ld默認是讀取4個字節數據,所以同上,它的讀取結果是

 

n1被按照4個字節讀取,n2也是,這樣連帶n3n4都被賦予原本是n2的數據,則原n3n4數據被丟棄。

他們都是按照%ld也就是整形數的方法讀取,0X0000’0000=0,而0X4808’0000=1074266112

這與輸出結果是相符的。

【由此可得關於printf的幾點注意問題:A. 當第一個參量輸出異常時,往往後面的參量都輸出異常。B. Printf向堆棧傳參時是按照參數數據類型傳遞的而非格式說明符;當讀取數據時候是以格式說明符讀取,此時與參數本身及其類型無關。】

 

 

 

 

II   關於Scanf的傳參方式及賦值方式

四、考察下列代碼

 

當輸入3.59的結果如下

 

這是因爲丙行中,%f的格式符與後面的int型參量類型不匹配所導致。

五、原因分析

Scanf首先要從外設得到輸入數據,這些數據的儲存方式是按照格式說明符所儲存的,在這裏與printf不同的是,%f說明輸入數據是按照float儲存的,它並不自動轉換爲double。由於輸入3.59,這兩個數按照IEEE754標準的float型儲存方式(32位,1個符號位,8個階碼位,23個尾數位)儲存,所以其數值爲


現在scanf將這兩個二進制數賦給rh兩個變量,巧合的是,int型在VC下也是4字節的,從而賦值是無截尾無損失的,於是


則在丁行中代入表達式得(pi被預編譯定義爲3.1415

 

由於在計算時候浮點均按照double型計算,double能精確表示數字個數是16~17位,可見輸出結果和計算結果的前18位是符合的,從而證明了其正確性。

 

【驗證】

現在我們再輸入6.253.5這兩個量,按照如上方式做預測計算:

首先按照IEEE754float型將這兩個量做轉換


全部賦給rh


代入丁行表達式得

 

下面用程序驗證結果

 

16位是符合的,這也符合double的精確表示數字個數範圍。

六、輸入流阻塞問題

繼續上面的程序,若輸入變成

 

即在兩個數之間加一個逗號,此時會發生如下情況

 

這是由於scanf的特性所造成的。其輸入機制包括一個鍵盤緩衝區。第一個%f要求scanf向鍵盤索要一個合法數,此時輸入3.5是正常的。而第二個%f同樣要求scanf向鍵盤索要一個合法數,而scanf得到的卻是3.5之後的一個逗號,這個逗號不是一個合法數,此時輸入異常,scanf會將逗號放回輸入流內(即鍵盤緩衝區內仍然是由一個逗號),且不將其消除,這就造成輸入流的阻塞:scanf一旦從鍵盤緩衝區讀取就會讀到逗號,這個逗號卻又不會被%f所接受,於是發生矛盾。這個時候sacnf結束,但是第二個參量h並沒有被有效賦值,此時它是一個隨機值。

我們利用intk來接收scanf函數的返回值,它的返回值是成功賦值的變量數目

 

分別查看k

    

左圖是正常輸入,k=2,表面scanf成功賦值兩個量;而右圖是異常輸入,k=1表明只成功賦值了一個變量,顯然是r=3.5,但是h並沒有成功賦值爲9

 

查看異常賦值後變量值

七、解決方案

1.常規輸入字符添加逗號

 

  得到

 

2.設置預寬用於截斷所需數值,用滯後符%*c來儲存逗號

 

  首先將輸入的數據先截取三位賦給r,顯然r=3.5,之後用一個%c來讀取逗號,但是由於有滯後符%*c,它不把逗號賦給任何參數,逗號之後的賦給h,得到

 

3.變量h的再賦值

 

得到


  第一個%f讀取時讀到非法字符逗號停止,3.5全部賦值給r,然後%c讀取逗號給h,顯然顯然h是一個錯誤數值,但是賦值仍然是成功的,之後9被第三個的%f讀取再次賦值給h

  於是結果正確。

  【注意!不可以寫爲%f %f %f,這是因爲逗號是字符,%f無法讀取字符從而造成輸入流阻

  塞,h不會被成功賦值的。】

  

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章