Q: What's wrong with this code?
char c;
while((c = getchar()) != EOF)
A:很多初学者都习惯用char型变量接收getchar、getc、fgetc等函数的返回值,其实这么做是不对的,并且隐含着足以致命的错误。
getchar等函数的返回值类型都是int型,当这些函数读取出错或者读完文件后,会返回EOF(在系统中,它并不是存在于文件中任何实际的end-of-file字符,而是没有任何字符可用的信号)。EOF是一个宏?,一个区别于getchar返回值的数据,它与所有可能的getchar返回的char值截然不同,标准规定它的值必须是一个int型的负数常量,通常编译器都会把EOF定义为-1。问题就出在这里,使用char型变量接收getchar等函数的返回值会导致对EOF的辨认出错,或者把好的数据误认为是EOF,或者把EOF误认为是好的数据。换言之,变量c被声明为char类型,而不是int类型,这意味着c无法容下所有可能的字符,特别是,可能无法容下EOF。所以getchar的返回值必需存储在比char大的变量中。
我们来看下此程序的运行过程。首先,因为fgetc等函数的返回值是int型的,当赋值给char型变量时,会发生降级,从而导致数据截断。
例如:
十进制 | int | char |
10 | 00 00 00 0A | 0A |
-1 | FF FF FF FF | FF |
-2 | FF FF FF FE | FE |
在此,我们假设int和char分别是32位和8位的。由上可得,从int型到char型,损失了3个字节的数据。而当我们要拿char型和int型比较的时候,char型会自动升级为int型。char型升级为int型后的值会因为它到底是signed char还是unsigned char而有所不同。不幸的是,如果我们没有使用signed或者unsigned来修饰char,我们便无从知晓char到底是指unsigned char还是指signed char,因为这是由编译器决定的。不过,无论char是signed的也好,unsigned的也罢,都不能改变使用char型变量接收fgetc等函数的返回值是错误的这个事实。唯一能改变的是该错误导致的后果。
1.
| char | unsigned | signed |
| 10 | 00 00 00 0A | 00 00 00 0A |
| FF | 00 00 00 FF | FF FF FF FF |
| FE | 00 00 00 FE | FF FF FF FE |
由上表可知,当char是unsigned的时候,其转换为int后的值是正数。也就是说,假如我们把c定义为char型变量,而编译器默认char为 unsigned char,那么表达式(c=fgetc(fp))!=EOF将永远成立,也就是说以下循环是一个死循环。
2.
读到这里,可能有些朋友会说:“那么我明确把c定义为signed char型的就没问题了吧!”很遗憾,就算把c定义为signed char,仍然是错误的。假设fgetc等函数读到一个字节的值为FF,那么返回值就是00 00 00 FF。把这个值赋值给 c 后,c的值变成FF,然后c的值为了和 EOF 比较,会自动升级为int型的值,也就是FF FF FF FF,从而导致表达式(c=fgetc(fp))!=EOF不成立,将跳出循环。即读到值为FF的字符,误认为EOF,也就是说循环在没有读完文件的情况下提前退出。