scanf("%f%c", ...) 对输入 100e 做了什么?

What does scanf("%f%c", ...) do against input `100e`?

考虑以下 C 代码(在线可用 io.c):

#include <stdio.h>
int main () {
  float f;
  char c;
  
  scanf ("%f%c", &f, &c);
  printf ("%f \t %c", f, c);

  return 0;
}

当输入100f时,输出100.000000 f。 但是,当输入100e时,它只输出100.000000,后面没有e。这里发生了什么? 100e 不是无效的浮点数吗?

来自 C 标准(6.4.4.2 浮点常量)

decimal-floating-constant:
    fractional-constant exponent-partopt floating-suffixopt
    digit-sequence exponent-part floating-suffixopt

exponent-part:
    e signopt digit-sequence
    E signopt digit-sequence

如果您将 printf 的调用更改为以下方式

printf ("%e \t %d\n", f, c);

你会得到输出

1.000000e+02     10

即变量c得到换行符'\n'.

似乎 scanf 的实现方式是将符号 e 解释为浮点数的一部分,尽管该符号后没有数字。

根据C标准(7.21.6.2 fscanf函数)

9 An input item is read from the stream, unless the specification includes an n specifier. An input item is defined as the longest sequence of input characters which does not exceed any specified field width and which is, or is a prefix of, a matching input sequence.278) The first character, if any, after the input item remains unread.

所以100e是一个浮点数匹配的字符输入序列

像那样在 "%f %c" 之间输入一个 space 并且当你要输入时确保在两个输入之间有一个 space。 我假设您只想打印一个字符。

这是(可以说)glibc bug

这种行为明显违反了标准。然而,它是由其他实现展示的。 Some people consider it a bug in the standard instead.

根据标准,输入项定义为最长的输入字符序列,不超过任何指定的字段宽度,并且是匹配输入序列的前缀。所以 100e 是一个输入项,因为它是匹配输入序列的前缀,例如 100e1,但输入中任何更长的字符序列都不是。此外,如果输入项不是匹配序列,指令执行失败:这种情况是匹配失败100e 不是匹配序列,因此标准要求指令失败。

标准无法告诉 scanf 接受 100 并继续从 e 开始扫描,正如某些人所期望的那样,因为 stdio 有一个有限的推回只有一个字符。因此,在读取 100e 之后,实现必须至少再读取一个字符,具体地说是一个换行符,然后将换行符和 e 都推回,但它并不总是这样做。

我想说这显然是一个非常模糊的灰色地带。

如果您是 C 库的实现者(或 X3J11 委员会的成员),您必须担心这类事情 — 有时会担心很多。你必须担心边缘情况,有时边缘情况可能特别急躁。

但是,您没有用“语言律师”标签标记您的问题,所以您可能不担心严格正确的官方解释。

如果您不是 C 库的实现者或 X3J11 委员会的成员,我会说:不要 担心这里的“正确”答案是什么!你不必担心,因为你不在乎,因为你会疯狂地编写对这个问题敏感的代码——正是因为它是一个如此明显的灰色地带。 (即使您 确实 弄清楚这里的正确行为是什么,您是否相信世界上每个 C 库的每个实现者总是会实现该行为?)

我想说在“不担心”的范畴内你可以做三件事,并且不要编写对这个问题敏感的代码。

  1. 根本不要使用 scanf(用于任何事情)。这是一个令人厌恶的、不精确的、不完美的函数,除了——也许——在你第一次学习 C 时将数字输入到你编写的前几个程序中之外,它没有任何用处。在那之后,scanf 在任何严肃的场合都没有用程序。

  2. 不要安排您的代码和数据,使其首先必须面对诸如“100e”之类的模棱两可的输入。无论如何,它是从哪里来的?它是用户可能输入的输入吗?从数据文件中读取数据?是预期的还是意外的,正确的还是错误的输入?如果您正在读取数据文件,您是否可以控制写入数据文件的代码?你能保证浮点字段总是被适当地定界吗,不会偶尔会附加随机字母字符吗?

  3. 如果您 do 必须解析可能包含有效浮点数的输入,可能会附加随机字母字符,因此可能会产生歧义,例如对此,我鼓励您改用 strtod,它可能定义得更好,实现也更好。