int ch = getchar()?

引言

也许你看过/写过类似这样的代码:

char ch;
while ((ch = getchar()) != EOF)
    putchar(ch);

这段代码看起来没什么问题,但实际上代码中的循环有可能永不终止,也有可能提前结束。

函数与EOF介绍

在解释之前,我们先了解下相关内容。C语言常用的字符读取函数如下:

int getchar(void);
int getc(FILE *stream);
int fgetc(FILE *stream);

这三个函数都是从文件流中读取一个字符,正常情况下把该字符从unsigned char转换为int类型返回,若出错或读到文件结尾则返回EOF

EOF又是什么呢?EOF只是一个宏定义的负整数而已,一般为-1(hex:0xffffffff)。

由于上述函数区别不大,此文便只用最常见的getchar函数来讨论。

char ch = getchar()错在哪里?

看到这,你可能会说:既然EOF只是值为-1的负整数,这不是在char的表示范围-128~127内吗?用char保存返回值有何不可?

-1真的一定在char的表示范围内吗?我们可以在头文件limits.h中看到如下定义:

/* Minimum and maximum values a `signed char' can hold.  */
#define SCHAR_MIN	(-128)
#define SCHAR_MAX	127

/* Maximum value an `unsigned char' can hold.  (Minimum is 0.)  */
#define UCHAR_MAX	255

/* Minimum and maximum values a `char' can hold.  */
#ifdef __CHAR_UNSIGNED__
    #define CHAR_MIN	0
    #define CHAR_MAX	UCHAR_MAX
#else
    #define CHAR_MIN	SCHAR_MIN
    #define CHAR_MAX	SCHAR_MAX
#endif

由此可见,标准并未规定char是有符号的,而是留给各个编译器自己实现。

所以当char默认为无符号时,-1就不在其表示范围内,这时使用char保存返回值也就有问题了。实际上即使你所用的编译器默认定义char是有符号的,文章开头的那段代码依然有潜在的bug。下面我们分为char默认无符号和有符号两种情况讨论,利用计算机组成原理的知识来详细解释为什么不应该用char保存getchar的返回值。

1. char默认是无符号的

这种情况会导致循环无法终止。假定我们遇到错误/读到文件末尾,getchar函数返回EOF,这时由于chchar类型,只有一个字节,而EOFint类型,有四个字节却要保存在一个字节中,EOF的值会被截断,ch也就等于0xff,又因为ch要与EOF比较,ch符号扩展得0x000000ff(因为ch是无符号的,也就是0扩展),永远不可能等于EOF,循环也就永远不会终止。

2. char默认是有符号的

这种情况可能导致循环提前终止。同样假定我们遇到错误/读到文件末尾,getchar函数返回EOF,前面都一样,但是在符号扩展的时候,由于ch是有符号的,其扩展得0xffffffff,等于EOF。因此循环是可以终止的,但是如果我们在遇到错误/读到文件末尾之前读到了一个值为0xff的字节,循环就会同碰到EOF一样终止。

正确的代码

那么如何修改代码才是正确的呢?其实只需要用int来保存返回值即可。

int ch;
while ((ch = getchar()) != EOF)
    putchar(ch);

这种情况就不会有问题。即使我们在遇到错误/读到文件末尾之前读到了一个值为0xff的字节,由于chint类型,而getchar是将读到的字符由unsigned char转为int作返回值,则该字节的值经符号扩展再赋值给chch就等于0x000000ff,不会等于EOF,也就不会提前终止了。

综上,这就是为什么要用int类型而不是char类型来保存C语言字符读取函数的返回值。

参考


本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!