TL;DR:
char c; c = getchar();
是错误,有缺陷和漏洞的。
int c; c = getchar();
是正确的。
如果读取到文件末尾,则getc
和fgetc
也同样适用于此规则,甚至更加适用。
始终将 getchar
(fgetc
, getc
...) (和 putchar
) 的返回值最初存储在类型为 int
的变量中。
putchar
的参数可以是任何 int
、char
、signed char
或 unsigned char
;它的类型并不重要,它们都能正常工作,即使某些情况下会导致传递给字符的正整数和负整数不同(包括 \200
(128) 及以上的字符)。
你
必须使用
int
来存储
getchar
和
putchar
的
返回值的原因是,当到达文件结尾条件(或发生I/O错误)时,它们都会返回宏
EOF
的值,这是一个负整数常量,
(通常为-1
)。对于
getchar
,如果返回值不是
EOF
,则它是读取的无符号字符
unsigned char
零扩展为
int
。也就是说,假设8位字符,返回的值可以是
0
...
255
或宏
EOF
的值;同样假设8位字符,没有办法将这257个不同的值压缩成256个,以便每个都可以唯一地标识。
现在,如果您将其存储为char
,则效果取决于字符类型默认是带符号还是无符号的!这因编译器和架构而异。如果char
是有符号的,并且假设EOF
定义为-1
,那么输入的EOF
和字符'\377'
都将与EOF
相等;它们将被符号扩展为(int)-1
。
另一方面,如果
char
是无符号的(在ARM处理器上,默认情况下是这样的,包括
树莓派系统;并且似乎对
AIX也是如此),那么没有任何值可以存储在
c
中与
-1
相等;包括
EOF
;而不是在
EOF
上退出,您的代码将输出一个单独的
\377
字符。
这里的危险在于使用有符号的
char
时,代码
似乎工作正常,但实际上仍然存在严重问题——其中一个合法输入值被解释为
EOF
。此外,C89、C99、C11并没有规定
EOF
的值;它只表示
EOF
是一个负整数常量;因此,在特定实现中,
EOF
可能不是
-1
,而是
-224
,这将导致空格的行为类似于
EOF
。
gcc
有一个开关
-funsigned-char
,可以用于在默认为有符号的平台上使
char
无符号。
% cat test.c
#include <stdio.h>
int main(void)
{
char c;
printf("Enter characters : ");
while ((c = getchar()) != EOF){
putchar(c);
}
return 0;
}
现在我们使用有符号的
char
运行它:
% gcc test.c && ./a.out
Enter characters : sfdasadfdsaf
sfdasadfdsaf
^D
%
看起来工作正常。但是使用未签名的 char
:
% gcc test.c -funsigned-char && ./a.out
Enter characters : Hello world
Hello world
���������������������������^C
%
那就是说,我尝试在那里多次按下 Ctrl-D 键,但每次按下 EOF 时都会打印出一个“�”而不是退出循环。
现在,再次针对有符号的 char 类型,它无法区分 Linux 上的 char 255 和 EOF,这会导致二进制数据等出现问题:
% gcc test.c && echo -e 'Hello world\0377And some more' | ./a.out
Enter characters : Hello world
%
只有第一部分,直到\0377
转义字符被写入标准输出。
请注意,字符常量和包含无符号字符值的int之间的比较可能不会按预期工作(例如,在ISO 8859-1中,字符常量'ä'将表示有符号值-28)。因此,假设您编写的代码将读取输入直到ISO 8859-1代码页中的'ä',则应执行以下操作。
int c;
while ((c = getchar()) != EOF){
if (c == (unsigned char)'ä') {
}
}
由于整数提升,所有的char值都适合于int,并且在函数调用时自动提升,因此您可以将任何int、char、signed char或unsigned char作为参数(而不是存储其返回值)传递给putchar,并且它将按预期工作。
实际传递给整数的值可能是正数甚至是负数;例如,字符常量\377在char为有符号的8位-char系统上将是负数;但是putchar(或实际上是fputc)将把该值转换为无符号char。
C11 7.21.7.3p2:
例如,
fputc
将保证将给定的
c
转换为
(unsigned char)c
。