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,这时由于ch是char类型,只有一个字节,而EOF是int类型,有四个字节却要保存在一个字节中,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的字节,由于ch是int类型,而getchar是将读到的字符由unsigned char转为int作返回值,则该字节的值经符号扩展再赋值给ch,ch就等于0x000000ff,不会等于EOF,也就不会提前终止了。综上,这就是为什么要用int类型而不是char类型来保存C语言字符读取函数的返回值。
参考
本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!