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 协议 ,转载请注明出处!