C語言中為什么不能用char類型來存儲getchar()的返回值


  在看 <<The C Programming Language>> 第二版時,看到1.5.1節第18頁有這樣一段從輸入拷貝到輸出的代碼:

#include <stdio.h>

/* copy input to output; 2nd version */
main()
{
    int c;
    c = getchar();
    while(c != EOF){
        putchar(c);
        c = getchar();
    }
}    

  直覺告訴我getchar返回值應該是char類型的,這個地方為什么不能用char類型來存儲getchar()的返回值呢?

  其實文中解釋的很清楚,可當時沒有看明白:

  在鍵盤或者屏幕上的字符都是用char類型存儲的,當然也可以用int類型來存儲。這個地方使用int來存儲字符有一個微妙但很重要的原因:為了把有效數據和輸入的結束(EOF)區分開來。getchar()在沒有更多輸入數據時返回一個特殊值,這個值不會跟任何實際的字符混淆。這個值稱為 EOF(end of file,文件結束)。我們必須把c變量聲明成一個大到足夠存儲任何getchar()返回的值的類型。我們不能用char類型,因為c必須大到足夠容納任意可能的char還有EOF。因此我們使用int類型。

  如果你看到這里就明白了,或者早就知道原因,那可以不用接着看了。下面是我理解這個原因的思路。

  1.  getchar的函數聲明

  雖然看着getchar(),直覺告訴我這應該返回char類型吧,但還是讓我們看看C語言中 getchar() 的函數聲明:  

int getchar ( void );

  嗯?返回值是int?(不靠譜的直覺啊)在Linux下輸入命令:man getchar(),結果更加詳細:

    NAME
       fgetc, fgets, getc, getchar, gets, ungetc - input of characters and strings
[...]
    DESCRIPTION
       fgetc()  reads  the  next character from stream and returns it as an unsigned char cast to an int, or EOF on
       end of file or error.

  這樣我們就明白了,getchar()從標准輸入(stdin)流中讀取一個字符,把它當作一個unsigned char,然后強制轉化成int類型來做為返回值,如果遇到文件末尾或者錯誤,返回EOF。

  2. EOF是什么

  用google搜索時,首先看到了這樣的一個帖子: EOF的定義和如何有效的使用它

  EOF不是:

  •  一個char類型 (a char)
  •     不是一個在文件末尾出現的值 (a value that exists at the end of a file)
  •     不是一個可能在文件中間出現的值(a value that could exist in the middle of a file)

  C99標准規定(見 7.19.1 Introduction):  

EOF which expands to an integer constant expression, with type int and a negative value, that
is returned by several functions to indicate end-of-file,that is, no more input from a
stream;

  好,我們明白了 EOF 是一個宏,展開后為一個整型常量表達式(integer constant expression),是int類型(C語言中整數常量是int類型的),而且值是負值。一些函數用它作為返回值,表示流中沒有更多的輸入。

  讓我們去定義它的頭文件<stdio.h>中去看看:  

#define EOF     (-1)

  那么 EOF 在計算機中十六進制表示形式是 0xFFFFFFFF(有符號數在計算機中是一般用補碼(two's-complement)表示)。通過getchar函數的定義,我們知道getchar() 從標准輸入(stdin)流中讀取一個unsigned char類型的字符0xXX,然后強制轉化成int 類型 0x000000XX(對無符號數,進行零擴展),此時這個值是大於等於零的。

  所以,EOF(0xFFFFFFFF)不可能出現在文件中間(文本文件中),它與字符(character)是截然不同的值。

  3.使用char類型存儲getchar()這類函數的返回值  

/* copy input to output; 2nd version */
main()
{
    char c;
    c = getchar();
    while(c != EOF){
        putchar(c);
        c = getchar();
    }
}    

  上述這段代碼中,c = getchar(); 會將getchar()的返回值int強制轉化為char類型,就將32位的int截斷為8位的char。之后的 c != EOF,又會將c強制轉化為int類型,就將8位的char類型進行擴展,擴展為32位int類型。在擴展時,如果char類型為無符號數,進行零擴展,如果char類型為有符號數,進行符號擴展。下面的兩個表分別展示了上面的這兩個轉換過程。為了制表方便,假設int是16位。  

---------------------------------      ----------------------------------------------
|    int到char轉化(截斷)                |      |              char到int轉化(擴展)      |
---------------------------------      ----------------------------------------------
| 十進制   |  int        |  char |      |  char |unsigned char=>int| signed char=>int|
|---------|-------------|-------|      |-------|------------------|-----------------|
|  2      |00 00 00 02  |  02   |      |  02   |  00 00 00 02     |00 00 00 02      |
|  1      |00 00 00 01  |  01   |      |  01   |  00 00 00 01     |00 00 00 01      |
|  0      |00 00 00 00  |  00   |      |  00   |  00 00 00 00     |00 00 00 00      |
| EOF(-1) |FF FF FF FF  |  FF   |      |  FF   |  00 00 00 FF     |FF FF FF FF      |
|  -2     |FF FF FF FE  |  FE   |      |  FE   |  00 00 00 FE     |FF FF FF FE      |
--------------------------------       ----------------------------------------------

  可見,如果char是無符號的,那么上面那段代碼中,當getchar()返回EOF時,c!=EOF 條件仍然滿足。此時程序不能正常終止。

  大家能不能自己寫代碼驗證一下C語言中從char到int的、int到char的強制類型轉化呢?

 PS:

  MSVC中char類型默認是有符號的char類型,可以在編譯時加入 /J 參數來把默認的char類型從signed char 改變到 unsigned char

  gcc中,char類型默認也是有符號的,可以在編譯時加入參數 -funsigned-char 或者 -fsigned-char 來指定char的符號類型。

參考資料:

  深入理解計算機系統 第二章 2.2 整數表示


如果您看了本篇博客,覺得對您有所收獲,請點擊右下角的“推薦”,讓更多人看到!

資助Jack47寫作,打賞一個雞蛋灌餅錢吧
pay_weixin
微信打賞
pay_alipay
支付寶打賞


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM