为什么多个EOF进入结束程序?

时间:2015-09-01 02:51:31

标签: c eof

试图了解我的代码的行为。我期待Ctrl-D导致程序打印数组并退出,但是需要3次按下,然后在第二次按下后进入while循环。

.box
  width: 100px
  height: 100px
  background: gray
  margin: 50px auto 0 auto
  border-color: #ff0000 #0000ff

我在这里阅读了这篇文章,但我并不十分理解。 How to end scanf by entering only one EOF

阅读完之后,我希望第一个Ctrl-D清除缓冲区,然后我期待c = getchar()获取第二个Ctrl-D并跳出。相反,第二个Ctrl-D进入循环并打印p和q,然后需要第三个Ctrl-D才能退出。

由于下面的代码在第一个Ctrl-D -

上消失,这更令人困惑
#include <stdio.h>
#include <stdlib.h>

void unyon(int p, int q);
int connected(int p, int q);

int main(int argc, char *argv[]) {
    int c, p, q, i, size, *ptr;

    scanf("%d", &size);

    ptr = malloc(size * sizeof(int));

    while((c = getchar()) != EOF){
        scanf("%d", &p);
        scanf("%d", &q);

        printf("p = %d, q = %d\n", p, q);
    }

    for(i = 0; i < size; ++i)
        printf("%d\n", *ptr + i);

    free(ptr);
    return 0;
}

2 个答案:

答案 0 :(得分:1)

如果通过调试器运行它,您将获得更清晰的图像。这是一系列事件。

  1. scanf("%d", &size);被召唤。
  2. 输入一个数字后跟ENTER。这里的关键是scanf不会消耗\n产生的ENTER
  3. getchar被调用。这会消耗\n
  4. scanf("%d", &p);被调用。这消耗了第一个ctrl-D。如果检查了返回值,那么很明显会发生错误。
  5. scanf("%d", &q);被调用。这消耗了第二个ctrl-D。
  6. 循环返回顶部并调用getchar。然后第三个ctrl-D导致EOF返回getchar,因此循环在那时突然出现。
  7. 我将把它留作练习,以解释为什么第二个程序按预期运行。

答案 1 :(得分:0)

这里有不同的东西搞乱。

首先,当您在输入终端输入Ctrl-D时, tty 驱动程序正在处理您的输入,在缓冲区中添加每个字符并处理特殊字符。其中一个特殊字符(Ctrl-D)表示接受最后一个字符并使它们全部可用于系统。这使得有两件事情发生:首先,从数据流中消除Ctrl-D字符;第二,到目前为止输入的所有字符都由进程系统调用提供为read(2)getchar()是一个缓冲的库调用,可以避免每个字符读取一次,允许在缓冲区中存储以前读取的字符。

此处混乱的另一件事是系统在 posix 系统(以及所有unix系统)中发出文件结尾的信号。当您进行read(2)系统调用时,返回值是读取的实际字符数(或-1,如果失败,但这与EOF无关,因为它将是很快解释)。 系统通过返回0个字符来标记文件结束条件。因此,操作系统标记文件结束read(2)返回0个字节(如果您只点击返回键,则会使\n出现在数据流中)。

这里弄乱的第三件事是来自getchar(3)函数的返回值的类型。它不返回char值。由于所有可能的字节值都可以为getchar(3)返回,因此不可能为EOF发送信号保留特殊值。该解决方案采用了很久很久以前(当设计getchar(3)时,就是C语言的第一个版本)(参见Brian Kernighan和Denis Ritchie的 C编程语言 ,第一版。)是使用int作为返回值,以便能够返回所有可能的字节值(0..255)加上一个额外的值,称为EOFEOF依赖于实现,但通常定义为-1(我认为即使标准现在指定它也必须定义为-1,但不确定)

因此,让所有事情协同工作,EOF是一个int常量,定义为允许程序员编写while ((c = getchar()) != EOF)。永远不会从终端获取-1作为数据值。系统始终通过使read(2)返回0来标记文件结束条件。接收Ctrl-D后的终端驱动程序只是将其从流中删除,并将数据提升到(但不包括Ctrl-JCtrl-M,换行和进位返回,相应地,它们也被解释并在数据流中输入为\n

所以,接下来的问题是:为什么通常需要两个(或更多)Ctrl-D个字符来表示 eof

是的,正如我所说,只有Ctrl-D(但不包括它)才能使内核可用,因此read(2)的结果可能与0第一次。但可以肯定的是,如果你按顺序两次输入Ctrl-D字符,那么在第一个之后,两个字符之间就不会有更多的字符,从而确保read()零字符。通常,程序处于循环中,执行多次读取

while ((n_read = read(fd, buffer, sizeof buffer)) > 0) {
    /* NORMAL INPUT PROCESSING GOES HERE, for up to n_read bytes
     * stored in buffer */
} /* while */
if (n_read < 0) {
    /* ERROR PROCESSING GOES HERE */
} else {
    /* EOF PROCESSING GOES HERE */
} /* if */

对于文件,行为是不同的,因为任何驱动程序都不会解释Ctrl-D(它存储在磁盘文件中),所以你会得到Ctrl-D作为普通字符(它是值为\004

当你读取一个文件时,通常这会读取大量完整的缓冲区,然后进行部分读取(小于缓冲区大小字节输入)和最终读取零字节,表示文件已经结束。

注意

根据某些unices中tty驱动程序的配置, eof 字符可以更改并具有不同的均值。 return 字符和换行字符也会发生。有关此问题的详细文档,请参见termios(3)手册页。