getchar/fgetc和putchar/fputc中int和char的区别是什么?

26

我正在尝试自学C语言,但是我对getcharputchar有点困惑:

1

#include <stdio.h>

int main(void)
{
    char c;
    printf("Enter characters : ");
    while((c = getchar()) != EOF){
      putchar(c);
    }
    return 0;
}

2

#include <stdio.h>

int main(void)
{
    int c;
    printf("Enter characters : ");
    while((c = getchar()) != EOF){
      putchar(c);
    }
    return 0;
}

C库函数int putchar(int c)将一个由参数char指定的字符(无符号char)写入stdout。

C库函数int getchar(void)从stdin获取一个字符(无符号char)。这相当于使用stdin作为其参数的getc。

这是否意味着putchar()接受intchar中的任何一个或者两个都可以,而对于getchar()应该使用int还是char


1
为什么用于保存getchar返回值的变量必须声明为int? - phuclv
@LưuVĩnhPhúc 或者相反。问题的年龄并不重要。 - Antti Haapala -- Слава Україні
2个回答

53

TL;DR:

  • char c; c = getchar();错误,有缺陷和漏洞的。
  • int c; c = getchar();正确的

如果读取到文件末尾,则getcfgetc也同样适用于此规则,甚至更加适用。


始终将 getchar (fgetc, getc...) (和 putchar) 的返回值最初存储在类型为 int 的变量中。

putchar参数可以是任何 intcharsigned charunsigned char;它的类型并不重要,它们都能正常工作,即使某些情况下会导致传递给字符的正整数和负整数不同(包括 \200 (128) 及以上的字符)。


必须使用int来存储getcharputchar返回值的原因是,当到达文件结尾条件(或发生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,这将导致空格的行为类似于EOFgcc有一个开关-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

@JonathanLeffler,我理解你写的一切。我不明白的是危险在哪里,因为signed char对EOF为负数和-1没有任何问题。我只是想说:如果int是unsigned int,它将面临完全相同的问题。那么有什么区别呢? - Judismar Arpini Junior
3
如果你生活在土耳其,特别是使用带有两个点的字母ÿ(y-umlaut,U+00FF,LATIN SMALL LETTER Y WITH DIAERESIS),将这个字母输入到保存getchar()结果的有符号 char 类型的代码中会被检测为 EOF,就像你输入了 Control-D(Unix)或 Control-Z(Windows)一样——它们表示“没有更多数据”或EOF。因此,问题在于一个合法的字符(ÿ)被视为EOF,而实际上它不应该被这样处理。这几乎和从不将任何东西视为EOF一样糟糕。 - Jonathan Leffler
1
标准规定(适用于 fgetc() ,但 getchar() 是基于 getc(stdin) 实现的,而 getc() 等效于 fgetc() )如下所述:如果流指针为 stream 的输入流的文件结束符指示器未设置且存在下一个字符,则 fgetc 函数将该字符作为转换为 intunsigned char 获取… 如果设置了流的文件结束符指示器或者流处于文件的结尾,则流的文件结束符指示器将被设置并且 fgetc 函数返回 EOF。 - Jonathan Leffler
1
'ä'这个问题对我来说是新的。看起来C11 §6.4.4.4 10是相关的引用:“如果一个整数字符常量包含一个单一的字符或转义序列,那么它的值就是当具有类型char且其值等于单个字符或转义序列的对象被转换为类型int时的结果。” - chux - Reinstate Monica
1
我曾经和某人争论过在一些评论中是否需要使用intchar来配合getchar()。下次我只需将他们引到这里! - ad absurdum
显示剩余3条评论

2
始终使用 int 来保存从 getchar() 获取的字符,因为 EOF 常量是 int 类型。如果使用 char 进行比较,则与 EOF 的比较不正确。
尽管如此,您仍然可以安全地将 char 传递给 putchar(),因为它会自动提升为 int
注意:从技术上讲,大多数情况下使用 char 可以正常工作,但这样就无法使用 0xFF 字符,因为由于类型转换,它们将被解释为 EOF。为了涵盖所有情况,始终使用 int。如 @Ilja 所说 - int 需要表示所有 256 个可能的字符值和 EOF,总共有 257 个可能值,这些值不能存储在 char 类型中。

在32位机器上,(int)-10xFFFFFFFF,超出了char的范围,但是(signed char)-10xFF,在比较时仍会被类型提升为int。这就是通常情况下它能够工作的原因,但如果您不使用int来存储字符,则不能将0xFF作为有效字符保存在流中。也就是说,使用int来存储它将被保存为0x000000FF,这与EOF不同。 - JohnLM
1
应该说“如果你期望在流中看到0xFF或者不知道char是有符号还是无符号的话,就使用int”,也就是说,“总是使用int”。 - Antti Haapala -- Слава Україні
3
“所以你的意思是最好读取一个字符值并将其存储在int变量中?”是的!直到你将其与EOF进行比较为止。之后,您可以将其存储为char。 但我也在说getchar()返回int,有它的原因。 - JohnLM
3
@JudismarJunior 接受的答案表达得很好:假设你正在尝试使用可表示仅256个符号的类型来表示257个符号,你需要256个字符+ EOF。Int型可以表示这些。 - Ilja Everilä
1
@Ilja,我现在明白了。再次感谢。 - Judismar Arpini Junior
显示剩余12条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接