【问题标题】:scanf what remains on the input stream after failerscanf 失败后输入流上剩余的内容
【发布时间】:2019-06-11 01:12:19
【问题描述】:

几年前我使用 K&R 第 2 版 ANSI C 学习了 C。我一直在复习我的笔记,同时我正在从其他 2 本书中学习更现代的 C。

我注意到 K&R 在书中从不使用 scanf,除了在他们介绍它的一节。他们主要使用他们在书中编写的 getline 函数,一旦他们引入指针,他们会在本书后面更改该函数。 getline 与 gcc getline 不同,这给我带来了一些问题,直到我将 getline 的名称更改为 ggetline。

查看我的笔记我发现了这句话:

这种简化很方便,表面上很吸引人,而且它 工作,就它而言。问题是scanf不能正常工作 在更复杂的情况下。在 7.1 节中,我们说过调用 putchar 和 printf 可以交错。并非总是如此 的 scanf:如果您尝试混合呼叫,您可能会遇到莫名其妙的问题 通过调用 getchar 或 getline 来 scanf。更糟糕的是,事实证明 scanf 的错误处理不足以满足许多目的。它告诉你 转换是否成功(更准确地说,它会告诉您 成功转换了多少次),但它并没有告诉您更多信息 比那个(除非你问得非常仔细)。像atoi和atof,scanf 在处理 %d 或 %f 输入时停止读取字符 找到一个非数字字符。假设您已提示用户 输入一个数字,用户不小心输入了字母“x”。扫描 可能返回 0,表示它不能转换数字,但是 不可转换的文本(“x”)保留在输入流中,除非您 想办法把它去掉。

出于这些原因(以及其他几个原因,我不会费心 提)通常建议不要使用scanf 非结构化输入,例如用户提示。读起来好多了 像 getline 这样的整行(正如我们一直在做的那样 沿)然后以某种方式处理该行。如果该行应该 是单个数字,您可以使用 atoi 或 atof 转换它。如果 行有更复杂的结构,你可以使用 sscanf (我们将 一分钟后见面)来解析它。 (使用 sscanf 比使用 scanf 更好 因为当 sscanf 失败时,您可以完全控制自己的操作 下一个。另一方面,当 scanf 失败时,您将受到 它在输入流中离开你的位置。)

起初我以为这句话来自 K&R,但我在书中找不到。然后我意识到那是我上网的讲义,为多年前使用 K&R 书籍教授课程的人写的。

lecture notes

我知道 K&R 的书现在已经 30 年了,所以在某些方面它已经过时了。

这句话很老了,所以我想知道 scanf 是否仍然存在这种行为或已经改变了?

scanf 失败时是否仍将内容留在输入流中?例如上面:

假设您提示用户输入一个数字,并且用户 不小心输入了字母“x”。 scanf 可能返回 0,表示 它无法转换数字,但不可转换的文本( 'x') 保留在输入流中。

以下是否仍然正确?

putchar 和 printf 可以交错。并非总是如此 的 scanf:如果您尝试混合呼叫,您可能会遇到莫名其妙的问题 通过调用 getchar 或 getline 来 scanf。

自从写了上面的引用之后,scanf 有很大的变化吗?或者他们今天仍然如此?

我问的原因是,在我正在阅读的新书中,没有人提到这些问题。

【问题讨论】:

  • scanf() 的行为没有显着改变——改变它会破坏工作代码(大量工作代码)。

标签: c scanf


【解决方案1】:

scanf() 是邪恶的 - 使用 fgets() 然后解析。


细节并不是scanf()完全坏了。

1) 格式说明符经常以弱的方式使用

char buf[100];
scanf("%s", buf); // bad - no width limit

2) 错误地不检查返回值

scanf("%99[\n]", buf); // what if use entered `"\n"`?
puts(buf); 

3) 当输入不符合预期时,不清楚stdin 中还剩下什么。

if (scanf("%d %d %d", &i, &j, &k) != 3) {
  // OK, not what is in `stdin`?
}

如果您尝试混合对 scanf 的调用与对 getchar 或 getline 的调用,则会遇到令人费解的问题。

是的。许多scanf() 调用在stdin 中留下尾随'\n',然后getline(), fgets() 将其读取为空行。 scanf() 用于阅读getline()fgets() 更适合阅读


自从上面的引用被写后,scanf 有很大的变化吗?

只有这么多的变化才能发生,而不会弄乱代码库。 @Jonathan Leffler

scanf() 仍然很麻烦。 scanf() 无法接受参数(在格式之后)来指示char * 目标接受多少个字符。

一些系统添加了额外的格式选项来提供帮助。

一个基本问题是这样的:

用户输入是邪恶的。将文本输入作为一个步骤,限定输入,然后解析和评估其成功比尝试在一个函数中完成所有这些操作更加稳健。


安全

scanf() 的弱点和程序员对scanf() 的编码倾向不佳一直是黑客的金矿。

IMO,C 缺乏强大的用户输入功能集。

【讨论】:

  • scanf 的行为在 C89 和 C99 之间也发生了变化。在 C89 中,使用 %f%g 读取后跟除 Ee 以外的任何字母的浮点数将读取数字部分并将字母(以及后面的所有内容)挂起。 C99 的 %f%g 现在将 0x 视为十六进制前缀,并且还将使用和接受 INFINFINITE,并且将在不接受 INFINITE 的任何前缀的情况下使用,后跟除正确的下一个字符。
  • @supercat 是的。其他奇怪的东西包括“INF or INFINITY, ignoring case”, “NAN or NAN(n-char-sequence opt), ignoring case in the NAN”(所以现在我们有语言环境和有效负载问题)。像"0x\n"这样的臀部输入(消耗“0”,离开“x”)、“%a"、“%Le"等。
猜你喜欢
  • 2010-12-21
  • 1970-01-01
  • 2013-05-19
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-06-30
  • 1970-01-01
  • 2019-08-20
相关资源
最近更新 更多