fscanf read()s 超过我要求的字符数

fscanf read()s more than the number of characters I asked for

我有以下代码:

#include <stdio.h>

int main(void)
{
  unsigned char c;

  setbuf(stdin, NULL);
  scanf("%2hhx", &c);
  printf("%d\n", (int)c);
  return 0;
}

我将 stdin 设置为无缓冲,然后让 scanf 读取最多 2 个十六进制字符。确实,scanf 照要求做了;例如,将上面的代码编译为 foo:

$ echo 23 | ./foo
35

但是,如果我strace这个程序,我发现libc实际上读取了3个字符。这是来自 strace 的部分日志:

$ echo 234| strace ./foo
read(0, "2", 1)                         = 1
read(0, "3", 1)                         = 1
read(0, "4", 1)                         = 1
35 # prints the correct result

所以 sscanf 给出了预期的结果。然而,这个被读取的额外字符是可检测的,它恰好破坏了我试图实现的通信协议(在我的例子中,GDB 远程调试)。

sscanf 的手册页说明了字段宽度:

Reading of characters stops either when this maximum is reached or when a nonmatching character is found, whichever happens first.

至少这看起来有点欺骗性;或者它实际上是一个错误?希望使用无缓冲的 stdin,scanf 可能读取的输入量不超过我要求的输入量是不是太过分了?

(我 运行 在 Ubuntu 18.04 上使用 glibc 2.27;我没有在其他系统上尝试过。)

This seems a bit deceptive, at least; or is it in fact a bug?

国际海事组织,没有。

An input item is read from the stream, ... 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. The first character, if any , after the input item remains unread. If the length of the input item is zero, the execution of the directive fails; this condition is a matching failure unless end-of-file, an encoding error, or a read error prevented input from the stream, in which case it is an input failure. C17dr § 7.21.6.2 9

"%hhx" 之类的代码(没有宽度限制)当然必须通过十六进制字符得到 1 才能知道它已经完成。多余的字符被 推回 stdin 以用于下一个输入操作。

“输入项之后的第一个字符(如果有的话)保持未读状态”对我来说意味着从最低级别的流中读取字符和从流中读取字符作为 的分离stream 可以 后推 至少 1 个字符,并将其视为“未读”。宽度限制为 2 不会保存代码,因为可以从 stream 中读取 3 个字符,而将 1 个推回。

2的宽度限制了要解释的最大字节长度,而不是限制最低级别读取的字符数。

Is it too much to hope that with unbuffered stdin, scanf might read no more than the amount of input I asked for?

是的。如果缓冲与否,我认为像 stdin 这样的 stream 允许 pushed-back 字符认为它们未读。

无论如何,"%2hhx" 预计读取的字符不超过 2 个,给定前导白色 - space 不算数。 “这些白色 space 字符不计入指定的字段宽度。”


“我将 stdin 设置为无缓冲”不会阻止 stream 读取多余的字符并稍后将其推回。


鉴于“正在读取的这个额外字符是可检测的,并且它恰好破坏了通信协议”,我推荐一种不使用 stream 的新方法。