fscanf读取了比我要求的字符数更多的字符

6
我有以下代码:
#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函数的man文档关于字段宽度的说明如下:

当达到最大宽度或找到一个不匹配的字符时,字符读取将停止,以先发生的为准。

这似乎有点欺骗性,或者说这实际上是一个bug吗?希望使用非缓冲stdin时,scanf函数是否能够仅读取我请求的输入数量?

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


我认为man手册中的“reading”是指相对于C流;也就是说,除了前两个字符外,其余字符仍然可以被scanfgetcfread等函数读取。 - Nate Eldredge
我认为POSIX 2.5.1允许这样做:“是否以及在什么条件下看到所有输入仅一次是由实现定义的。” - Nate Eldredge
@NateEldredge 这个评论是指由“fork”创建的文件句柄对。 - Reuben Thomas
1个回答

1
这似乎有点欺骗性,或者说这实际上是一个 bug 吗?
在我看来不是。
从流中读取一个输入项...输入项被定义为不超过任何指定字段宽度的最长输入字符序列,且是匹配输入序列或其前缀。如果输入项的长度为零,则指令执行失败;除非文件结束、编码错误或读取错误阻止了从流中获取输入,在这种情况下,它是匹配失败。C17dr §7.21.6.2 9。第一个字符(如果有)在输入项之后保持未读状态。例如像 "%hhx"(没有宽度限制)的代码必须获取一个十六进制字符才能知道它已完成。这个多余的字符被“推回”到 stdin 中以供下一次输入操作使用。
"The first character, if any, after the input item remains unread" implies that characters are read from the stream independently and not as a continuous stream. At least one character can be pushed back and considered "unread". The width limit of 2 only limits the maximum number of bytes to interpret, not the number of characters read at the lowest level.
It is not possible for scanf to read only the amount of input requested, whether buffered or unbuffered. Characters can be pushed back and considered unread in a stream like stdin.
Using "%2hhx" is risky because leading white-space characters do not count towards the specified field width and more than two characters may be read.
“I set stdin to be unbuffered”并不能阻止流从读取过多的字符并将其推回。
鉴于“可以检测到正在读取的这个额外字符,并且它恰好破坏了通信协议”,我建议采用一种不使用流的新方法。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接