1. 程式人生 > >關於C語言中printf函式“輸出歧視”的問題

關於C語言中printf函式“輸出歧視”的問題

目錄

關於C語言中printf函式“輸出歧視”的問題

問題描述

昨天晚上被問到一個問題,為什麼在同一個printf函式中兩次輸出一個double型變數會得不到正確的結果。具體程式碼大致如下:

#include <stdio.h>

int main()
{
    int a;
    double b;
    double result;

    printf("Please input a: ");
    scanf("%d", &a);

    printf("Please input b: ");
    scanf("%lf", &b);

    printf("\n");

    result = a * b;
    printf("result_int: %d\nresult_double: %f\n", result, result);

    return 0;
}

看主函式的第18行語句,其中result作為一個double型變數,被以整型浮點型分別輸出了一次,按照正常思路,輸出應該是下面這樣:

Please input a: 12
Please input b: 12.3

result_int: 858993460
result_double: 147.600000

即前一個按整型輸出時,會按照double型低32位的資料,輸出一個不期望的整數。

但是第二次輸出時,則會按照其正確的形式(64位8位元組double型別)輸出一個預期中的浮點數:147.600000

結果卻大出意料,實際輸出是下面這樣:

Please input a: 12
Please input b: 12.3

result_int: 858993460
result_double: 0.000000

探索問題原因

這樣的結果讓人很意外,第一時間就想到了是否是因為result本身出了問題,所以在原程式後加入了一句printf("\n%f\n", result);來確認result是否發生了意料之外的變化。

但是很顯然,並不是result出了問題,因為單獨輸出的result依然是正常的147.600000

那麼接下來,既然result本身沒有問題,就應當想到是否在輸出上出了問題。比如很顯然地,%d格式是標準的Int型輸出格式,正常來講應該是32位資料,而double則是64位資料,是否是這裡出了問題呢?

比如在同一句輸出裡面,對同一個變數多次不完整輸出,導致了其地址的遞進式輸出。

當然,到此時,這只是我的一種猜測。

要驗證這個想法,最簡單的莫過於直接檢視記憶體內容。

如圖

轉換為4位元組整數顯示結果為
與輸出

result_lower_32 = 858993459
result_higher_32 = 1080193843
resule_now = 147.600000

相符。

到此,產生這一問題的原因已經弄清楚了,並且最終問題應該是出在printf函式的定義中,看來還是C語言基礎不牢,今天開始有必要重新學習,認真研究其每個細節。

另一種研究方法

實際上一開始因為不太熟悉VS的除錯操作,並沒能很快地在除錯介面找到方法檢視具體變數的記憶體地址及其內容,原因一則是很久沒有用C語言了,二則當初學的時候也沒有認真,所以驗證這個問題的過程十分艱辛,

中間還自己寫了個函式想要輸出資料的二進位制格式,但是這個函式針對整型時工作正常,換成double型之後,由於沒想到辦法對double型和unsigned long long型直接進行位運算,所以迫不得已對double型資料進行了強制型別轉換,最終發現強制型別轉換會導致資料變化,無法保持其原型,所以這一思路作廢。就此略過不表。

後來想起來C語言中可以對資料直接按16進位制格式輸出,故設計瞭如下程式進行嘗試。

#include <stdio.h>

int main()
{
    //該系統中,double為64位8位元組,int和long為32位4位元組。
    //且為小端系統,即低位資料儲存在記憶體的低位地址中。
    double b;

    printf("Please input the values of b:\n");
    scanf("%lf", &b);
  
    printf("\n");
    printf("These are two parts of b:\n");
    printf("\tlower byte = \t%#x\n\thigher byte = \t%#x\n\n", b, b);

    printf("This is the whole b:\n");
    printf("\t%#llx\n", b);

    return 0;
}

輸出如下(輸入為12.3)

Please input the values of b:
12.3

These are two parts of b:
        lower byte =    0x9999999a
        higher byte =   0x40289999

This is the whole b:
        0x402899999999999a

問題結論

從上述兩種研究方法的結果,我們可以明顯看到,在同一個輸出格式字串中,倘若針對某一個待輸出量的轉換說明不足以對應其所有的資料位,則printf函式僅僅按照轉換說明對應的資料位數,從低到高,依次輸出。

所以對於介於初學和中級之間的學習者而言,研究stdio.h標頭檔案的原始碼函式很有必要的。