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

2024-01-02

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

#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;
}

我读了这里的帖子,但不太明白。如何通过仅输入一个EOF来结束scanf https://stackoverflow.com/questions/19889296/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>

main() {

    int c, nl;

    nl = 0;
    while((c = getchar()) != EOF)
        if (c == '\n')
            ++nl;
    printf("%d\n", nl);
}

让我们将程序简化为进行输入的调用:

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

这绝对不是正确的方法。稍后我们就会了解正确的用法。现在,我们只分析一下发生了什么。准确理解如何进行很重要scanf作品。这%d格式代码会使其首先跳过任何空白字符,然后读取可以转换为十进制整数的字符。最终将读取一些不属于十进制整数的字符;最有可能是换行符。因为格式字符串现已完成,所以刚刚读取的未使用的字符将被重新插入流中.

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

现在,假设您运行该程序并输入数字42然后按 Enter 键,然后按 Ctrl-D 关闭输入流。

The 42将由语句 1 读取,并且(如上所述)换行符将由语句 2 读取。因此,当执行语句 3 时,不再有数据可读取。因为在读取任何数字之前会发出文件结束信号,scanf将返回EOF。但是,代码没有测试返回值scanf;继续到语句 4。

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

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


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

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

但还有一个更微妙的错误,虽然在这种情况下影响不大,但一般来说可能很严重。读取的字符getchar语句 2 中的内容被简单地丢弃,无论它是什么。通常它是空白,所以它被丢弃并不重要,但你实际上并不知道,因为该字符被丢弃。也许这是一个逗号。也许那是一封信。也许它是什么很重要。

依赖于这样的假设:无论字符被读取,这是不好的风格。getcharat 语句 2 并不重要。如果您确实需要查看下一个字符,则应该将其重新插入输入流中,就像scanf does:

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 的行为。有一个非常长期存在的错误报告 https://sourceware.org/bugzilla/show_bug.cgi?id=1190由一个链接到回答OP中引用的问题 https://stackoverflow.com/a/19890073/1566221。如果您不厌其烦地阅读 bugzilla 线程中的最新帖子,您会发现一个链接glibc 开发者邮件列表上的讨论 http://sourceware.org/ml/libc-alpha/2012-09/msg00343.html.

我给个TL;DR版本吧,省去你数字考古的麻烦。从C99开始,标准就明确了EOF是“粘性的”。 §7.21.3/11 规定所有输入的执行就像连续字节被读取一样fgetc:

...字节输入函数从流中读取字符,就像连续调用fgetc功能。

§7.21.7.1/3 规定fgetc回报EOF如果设置了流的文件结束指示符,则立即:

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

因此,一旦设置了文件结束指示符,由于检测到文件结束或发生某些读取错误,后续的输入操作must立即返回EOF而不尝试从流中读取。有多种方法可以清除文件结束指示器,包括clearerr, seek, and ungetc;一旦文件结束指示器被清除,下一个输入函数调用将再次尝试从流中读取。

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

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

尽管有一个令人信服的论点(至少对我来说是令人信服的),即修复该错误不太可能破坏任何重要的东西,但仍然有一定程度的不愿意对此采取任何行动。因此,十年后,我们看到了一个仍然悬而未决的错误报告和一个不合格的实现。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

为什么要进入结束程序多个EOF? 的相关文章

  • boost::multi_index_container 复合键中的 equal_range 与比较运算符

    我正在尝试从多索引容器查询结果 其中值类型是三个元素的结构 第一个值已给出 但第二个和第三个值必须大于或小于查询参数 经过搜索后 我发现必须实现自定义密钥提取器 并且这里的一些链接建议相同 但我无法实现它 boost multi index
  • 在 LINQ 查询中返回不带时间的日期

    我正在编写一个查询 我想计算按日期联系我们的呼叫中心的次数 看起来很简单 但由于联系日期字段是日期时间字段 我得到了时间 因此当我按联系日期 时间 分组时 每个联系日期实例的计数为 1 所以 我想只按日期分组 而不按时间分组 下面是我用来查
  • 嵌入式系统中的malloc [重复]

    这个问题在这里已经有答案了 我正在使用嵌入式系统 该应用程序在 AT91SAMxxxx 和 cortex m3 lpc17xxx 上运行 我正在研究动态内存分配 因为它会极大地改变应用程序的外观 并给我更多的力量 我认为我唯一真正的路线是为
  • 使用 Microsoft Graph API 订阅 Outlook 推送通知时出现 400 错误请求错误

    我正在尝试使用 Microsoft Graph API 创建订阅以通过推送通知获取 Outlook 电子邮件 mentions 我在用本文档 https learn microsoft com en us graph api subscri
  • 如何在我的应用程序中使用 Windows Key

    Like Windows Key E Opens a new Explorer Window And Windows Key R Displays the Run command 如何在应用程序的 KeyDown 事件中使用 Windows
  • 写入和读取文本文件 - C# Windows 通用平台应用程序 Windows 10

    有用 但在显示任何内容之前 您必须在文本框中输入内容 我想那是因为我使用了 TextChanged 事件处理程序 如果我希望它在没有用户交互的情况下显示文本文件的内容 我应该使用哪个事件处理程序 因此 我想在按下按钮时将一些数据写入 C W
  • A* 之间的差异 pA = 新 A;和 A* pA = 新 A();

    在 C 中 以下两个动态对象创建之间的确切区别是什么 A pA new A A pA new A 我做了一些测试 但似乎在这两种情况下 都调用了默认构造函数 并且仅调用了它 我正在寻找性能方面的任何差异 Thanks If A是 POD 类
  • 如何在 Team Foundation 上强制发表有意义的签入评论?

    我有一个开发团队有一个坏习惯 他们写道poor签入评论 当我们必须在团队基础上查看文件的历史记录时 这使得它成为一场噩梦 我已经启用了变更集评论政策 这样他们甚至可以在签到时留下评论 否则他们不会 我们就团队的工作质量进行了一些讨论 他们很
  • 使用 LINQ 查找列表中特定类型的第一个元素

    使用 LINQ 和 C 在元素列表中查找特定类型的第一个项目的最短表示法是什么 var first yourCollection OfType
  • 是否有比 lex/flex 更好(更现代)的工具来生成 C++ 分词器?

    我最近将源文件解析添加到现有工具中 该工具从复杂的命令行参数生成输出文件 命令行参数变得如此复杂 以至于我们开始允许它们作为一个文件提供 该文件被解析为一个非常大的命令行 但语法仍然很尴尬 因此我添加了使用更合理的语法解析源文件的功能 我使
  • 初始化变量的不同方式

    在 C 中初始化变量有多种方法 int z 3 与 int 相同z 3 Is int z z 3 same as int z z 3 您可以使用 int z z 3 Or just int z 3 Or int z 3 Or int z i
  • Windows 10 中 Qt 桌面应用程序的缩放不当

    我正在为 Windows 10 编写一个简单的 Qt Widgets Gui 应用程序 我使用的是 Qt 5 6 0 beta 版本 我遇到的问题是它根本无法缩放到我的 Surfacebook 的屏幕上 这有点难以判断 因为 SO 缩放了图
  • 像“1$”这样的位置参数如何与 printf() 一起使用?

    By man I find printf d width num and printf 2 1 d width num 是等价的 但在我看来 第二种风格应该与以下相同 printf d num width 然而通过测试似乎man是对的 为什
  • 更改窗口的内容 (WPF)

    我创建了一个简单的 WPF 应用程序 它有两个 Windows 用户在第一个窗口中填写一些信息 然后单击 确定 这会将他们带到第二个窗口 这工作正常 但我试图将两个窗口合并到一个窗口中 这样只是内容发生了变化 我设法找到了这个更改窗口内容时
  • 用 C 实现 Unix shell:检查文件是否可执行

    我正在努力用 C 语言实现 Unix shell 目前正在处理相对路径的问题 特别是在输入命令时 现在 我每次都必须输入可执行文件的完整路径 而我宁愿简单地输入 ls 或 cat 我已经设法获取 PATH 环境变量 我的想法是在 字符处拆分
  • AccessViolationException 未处理

    我正在尝试使用史蒂夫 桑德森的博客文章 http blog stevensanderson com 2010 01 28 editing a variable length list aspnet mvc 2 style 为了在我的 ASP
  • 检查 url 是否指向文件或页面

    我们需要以下内容 如果文件确实是文件 则从 URL 下载该文件 否则 如果它是一个页面 则什么也不做 举个简单的例子 我有以下命令来下载文件 My Computer Network DownloadFile http www wired c
  • 将应用程序从 Microsoft Access 迁移到 VB 或 C#.NET

    我目前正试图说服管理层需要将我们的应用程序之一移植到 NET 该应用程序已经发展成为 Access 中的一个庞然大物 SQL 后端 拥有 700 个链接表 650 个表单 子表单 130 个模块和 850 个查询 我几乎知道这样做的所有主要
  • 如何在 C# 中播放在线资源中的 .mp3 文件?

    我的问题与此非常相似question https stackoverflow com questions 7556672 mp3 play from stream on c sharp 我有音乐网址 网址如http site com aud
  • 如何将字符串“07:35”(HH:MM) 转换为 TimeSpan

    我想知道是否有办法将 24 小时时间格式的字符串转换为 TimeSpan 现在我有一种 旧时尚风格 string stringTime 07 35 string values stringTime Split TimeSpan ts new

随机推荐