c/c++语言开发共享为什么多个EOF进入结束程序?

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

#include  #include  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 = %dn", p, q); } for(i = 0; i < size; ++i) printf("%dn", *ptr + i); free(ptr); return 0; } 

我在这里看了这篇文章,但我不太明白。 如何通过只输入一个EOF来结束scanf

读完之后,我期待第一个Ctrl-D清除缓冲区,然后我期待c = getchar()拿起第二个Ctrl-D然后跳出来。 而是第二个Ctrl-D进入循环并打印p和q,然后第三个Ctrl-D退出。

由于以下代码在第一个Ctrl-D-上退出,这使得更加困惑

 #include  main() { int c, nl; nl = 0; while((c = getchar()) != EOF) if (c == 'n') ++nl; printf("%dn", nl); } 

    让我们将程序剥离到输入的调用:

     scanf("%d", &size); // Statement 1 while((c = getchar()) != EOF){ // 2 scanf("%d", &p); // 3 scanf("%d", &q); // 4 } 

    这绝对不是要走的路; 我们将在一点点得到正确的用法。 现在,让我们分析一下发生了什么。 了解scanf工作原理非常重要。 %d格式代码使它首先跳过任何空白字符,然后只要字符可以变成十进制整数就读取字符。 最终会读取一些不是十进制整数的字符; 最有可能是换行符。 由于格式字符串现在已完成,因此刚刚读取的未使用字符将重新插入到流中

    因此,当调用getchargetchar将读取并返回终止整数的换行符。 在循环内部,然后有两个scanf("%d")调用scanf("%d") ,每个调用的行为如上所示:跳过空格(如果有),读取十进制整数,然后将未使用的字符重新插入输入流。

    现在,让我们假设你运行程序,然后输入数字42然后输入回车键,然后按Ctrl-D关闭输入流。

    42将由语句1读取,并且(如上所述)新行将由语句2读取。因此,当执行语句3时,不再有数据要读取。 由于在读取任何数字之前都会发出文件结尾信号,因此scanf将返回EOF 。 但是,代码不测试scanf的返回值; 它继续陈述4。

    此时应该发生的是语句4中的scanf应该立即返回EOF而不尝试读取更多输入。 这就是C标准所说的应该发生的事情,也就是Posix所说的应该发生的事情。 一旦在流上发出文件结束信号,任何输入请求应立即返回EOF直到手动清除文件结束指示符。 (参见下面的标准报价。)

    但是glibc,由于我们不会进入的原因,不符合标准。 它试图再次阅读。 因此用户必须输入另一个Ctrl-D,这将导致语句4处的scanf返回EOF 。 同样,代码不检查返回代码,因此它继续使用while循环并在语句2处再次调用getchar 。由于相同的错误, getchar不会立即返回EOF ,而是尝试从终端读取字符。 因此,用户现在必须键入第三个Ctrl-D以使getchar返回EOF 。 最后,代码检查返回码,while循环终止。


    这就是对正在发生的事情的解释。 现在,很容易在代码中看到至少一个错误:从不检查scanf的返回值。 这不仅意味着错过了EOF ,还意味着忽略输入错误。 (如果无法将输入解析为整数,则scanf将返回0.)这很严重,因为如果scanf无法成功匹配格式代码,则相应参数的值是未定义的 ,不得使用。

    简而言之: 始终检查来自*scanf返回值 。 (和其他I / O库函数。)

    但是也有一个更微妙的错误,这在这种情况下几乎没有什么区别,但总的来说可能是严重的。 getchar在语句2中读取的字符被简单地丢弃,无论它是什么。 通常它会是空格,所以它被丢弃并不重要,但你实际上并不知道它是因为字符被丢弃了。 也许这是一个逗号。 也许这是一封信。 也许这很重要。

    依赖于语句2中getchar读取的任何字符都是不重要的假设是不好的。 如果你真的需要查看下一个字符,你应该将它重新插入到输入流中,就像scanf一样:

     while ((c = getchar()) != EOF) { ungetc(c, stdin); /* Put c back into the input stream */ ... } 

    但实际上,那个测试并不是你想要的。 正如我们已经看到的那样, getchar在这一点上返回EOF可能性极小。 (这是可能的,但这是不太可能的)。 更可能的是getchar将读取换行符,即使下一个scanf将遇到文件结尾。 所以对下一个角色绝对没有任何意义; 正确的解决方案是检查scanf的返回码,如上所示。

    把它放在一起,你真正想要的是更像:

     /* No reason to use two scanf calls to read two consecutive numbers */ while ((count = scanf("%d%d", &p, &q)) == 2) { /* Do something with p and q */ } if (count != EOF) { /* Invalid format. Issue an error message, at least */ } /* Do whatever needs to be done at the end of input. */ 

    最后,让我们来看看glibc的行为。 有一个非常长期的错误报告与OP中引用的问题的答案相关联。 如果您麻烦地阅读bugzilla线程中的最新post,您将找到glibc开发人员邮件列表上讨论的链接。

    让我给出TL; DR版本,为您省去数字考古的麻烦。 自C99以来,标准已明确表明EOF是“粘性的”。 §7.21.3/ 11声明所有输入都被执行,好像fgetc读取了连续的字节:

    …字节输入函数从流中读取字符,就好像通过连续调用fgetc函数一样。

    并且§7.21.7.1/ 3声明如果设置了流的文件结束指示符, fgetc立即返回EOF

    如果设置了流的文件结束指示符,或者流位于文件结尾,则设置流的文件结束指示符,并且fgetc函数返回EOF 。 否则, fgetc函数返回stream指向的输入流中的下一个字符。 如果发生读取错误,则设置流的错误指示符,并且fgetc函数返回EOF

    因此,一旦设置了文件结束指示符,由于检测到文件末尾或发生了一些读取错误,后续输入操作必须立即返回EOF而不尝试从流中读取。 各种各样的东西可以清除文件末尾的指示符,包括更清晰, clearerr seekungetc ; 一旦文件结束指示符被清除,下一个输入函数调用将再次尝试从流中读取。

    然而,它并不总是那样。 在C99之前,从已经返回EOF的流中读取的结果未指定。 不同的标准库选择以不同的方式处理它。

    因此决定不改变glibc以符合(当时)新标准,而是保持与某些其他C库(尤其是Solaris)的兼容性。 (错误报告中引用了glibc源代码中的注释。)

    虽然有一个令人信服的论点(至少对我来说很有说服力),修复bug不太可能打破任何重要的事情,但仍然有一些不愿意做任何事情。 因此,十年之后,我们将在这里发布一个仍然开放的错误报告和一个不合规的实现。

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

    我将把它作为练习让你解释为什么第二个程序按预期运行。

    这里有不同的东西搞乱。

    首先,当你输入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 )加上一个额外的值,称为EOF的确切值取决于实现,但通常定义为-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顺序输入两次Ctrl-D char,则在第一次之后两个字符之间没有更多的字符,确保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作为普通字符(它的值是04

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

    注意

    根据某些unices中tty驱动程序的配置, eof字符可以更改并具有不同的均值。 也会出现返回字符和换行符 。 Se termios(3)手册页有关此的详细文档。

      以上就是c/c++开发分享为什么多个EOF进入结束程序?相关内容,想了解更多C/C++开发(异常处理)及C/C++游戏开发关注计算机技术网(www.ctvol.com)!)。

      本文来自网络收集,不代表计算机技术网立场,如涉及侵权请联系管理员删除。

      ctvol管理联系方式QQ:251552304

      本文章地址:https://www.ctvol.com/c-cdevelopment/518954.html

      (0)
      上一篇 2020年12月5日
      下一篇 2020年12月5日

      精彩推荐