"fscanf中“读取字符数”的范围是多少?"

8

fscanf()指定"%n"指令作为一种方式,用于在C11dr §7.21.6.2 12中编写“到目前为止通过此调用fscanf函数从输入流中读取的字符数”。让我们称这个数字为:ncount


"%n"指令可以带有长度修饰符hh、h、l、ll、j等。例如:

FILE *stream = stdin;
int n_i;
fscanf(stream, "%*s%n", &n_i);    // save as int
signed char n_hh;
fscanf(stream, "%*s%hhn", &n_hh); // save as signed char
long long n_ll;
fscanf(stream, "%*s%lln", &n_ll); // save as long long

什么是 ncount 的最小期望范围或类型?当从输入流中读取的字符数目很大时会发生什么或应该发生什么?
我的研究结果:
C规范对 ncount 的最小范围/类型定义没有明确规定。通过 "%n" 通常可以保存 ncount,它指定了一个 int 类型的目标,但不是一个 int 类型的源。
通过实验,ncount 在我的平台上似乎被视为 intlong 类型——这并不奇怪。 (我的 int/long/long long 分别占用 4/4/8 字节。)将 ncount 保存到 long long 中时,保存的值不超过 INT_MAX/LONG_MAX。 将 ncount 赋给 long long 时,可以将其设置为 unsigned 类型,以获得两倍的可用范围,但这是一个极端情况,可能未被实现者考虑。
我的测试表明,即使将 ncount 保存为 long long,也没有扩展其超出 int 范围的范围。
我的兴趣源于使用 "%*[^\n]%lln" 来确定(极端)行长度。
实现注意事项:
GNU C11(GCC)版本 6.4.0(i686-pc-cygwin),由 GNU C 版本 6.4.0、GMP 版本 6.1.2、MPFR 版本 3.1.5-p10、MPC 版本 1.0.3、isl 版本 0.14 或 0.13 编译。
glibc 2.26 发布。
Intel Xeon W3530,64 位操作系统(Windows 7)。
测试代码
#include <limits.h>
#include <stdio.h>
#include <string.h>

int print(FILE *stream, long long size, int ch) {
  char buf[4096];
  memset(buf, ch, sizeof buf);
  while (size > 0) {
    size_t len = size < (long long) sizeof buf ? (size_t) size : sizeof buf;
    size_t y = fwrite(buf, 1, len, stream);
    if (len != y) {
      perror("printf");
      return 1;
    }
    size -= len;
  }
  return 0;
}

int scan(FILE *stream) {
  rewind(stream);
  long long n = -42;
  int cnt = fscanf(stream, "%*s%lln", &n);
  printf("cnt:%d n:%lld ", cnt, n);
  return cnt != 0;
}

int testf(long long n) {
  printf("%10lld ", n);
  FILE *f = fopen("x.txt", "w+b");
  if (f == NULL) {
    perror("fopen");
    return 1;
  }
  if (print(f, n, 'x')) {
    perror("print");
    fclose(f);
    return 2;
  }
  if (scan(f)) {
    perror("scan");
    fclose(f);
    return 3;
  }
  fclose(f);
  puts("OK");
  fflush(stdout);
  return 0;
}

int main(void) {
  printf("%d %ld %lld\n", INT_MAX, LONG_MAX, LLONG_MAX);
  testf(1000);
  testf(1000000);
  testf(INT_MAX);
  testf(INT_MAX + 1LL);
  testf(UINT_MAX);
  testf(UINT_MAX + 1LL);
  testf(1);
  return 0;
}

测试输出

2147483647 2147483647 9223372036854775807

File length      Reported bytes read
      1000 cnt:0 n:1000 OK
   1000000 cnt:0 n:1000000 OK
2147483647 cnt:0 n:2147483647 OK
2147483648 cnt:0 n:-2147483648 OK  // implies ncount is internally an `int/long`
4294967295 cnt:0 n:-1 OK
4294967296 cnt:0 n:-1088421888 OK  // This `n` value may not be consistent. -1 also seen
         1 cnt:0 n:1 OK

[编辑]

通过运行 testf(UINT_MAX + 1LL); 函数的一些次数,我收到了其他不一致的结果,如“4294967296 cnt:0 n:1239482368 OK”。嗯。

fscanf() 的示例支持源代码使用一个 int 来表示 ncount


我在投票支持一个有趣的问题和不投票之间犹豫不决,因为如果你的输入很奇特,你应该编写自己的解析而不是使用fscanf。尽管如此,我期望答案是C 2011 5.2.4.1:“实现应该能够翻译和执行至少一个程序……”你的程序不是那个程序。 - Eric Postpischil
1
我同意你对标准的分析。它没有指定必须支持多大的 ncount。但是,它确实指定了 ll(和 zt)长度说明符可以出现在 n 指令中,当然你是正确的,这描述了接收变量的大小,而不是源计数。 - John Bollinger
@EricPostpischil 使用 fscanf() 时,在读取之前无法确定输入是否异类。问题不在于输入是否异类,而是 "%*s%n" 对异类输入不具有弹性。例如,"%1000s%n" 可以处理异类输入 - 只要 ncount 至少为10位即可。 - chux - Reinstate Monica
3
标准还规定,如果转换结果无法表示为目标对象,那么行为是未定义的(对于所有转换)。既然我没有看到有关ncount可容纳多大的其他限定条件,如果报告的计数受限于目标对象类型的容量限制更小,则我倾向于将其视为实现缺陷。 - John Bollinger
@chux 我在我的64位ArchLinux上使用这两个代码(实际代码和我错误的编辑)都能得到正确的输出(不是-1或垃圾)(unsigned is 32bit) https://pastebin.com/3Py5w5cr。 - Stargateur
显示剩余5条评论
1个回答

5
“type”或“ncount”的最小预期范围是多少?
标准没有指定任何特定的最小值。它明确表示:
“相应的参数必须是指向有符号整数的指针,该指针将被写入到该调用fscanf函数时从输入流中读取的字符数。”
这样一来,符合规范的实现就没有存储不同数字的变量的空间,除非标准对所有转换(包括%n)都指定:
“如果转换的结果不能在[目标]对象中表示,则其行为是未定义的。”
C2011, 7.21.6.2/12
C2011 7.21.6.2/10
当“从输入流中读取的字符数”较大时,会发生什么或应该发生什么?
如果与指令长度说明符(或缺少说明符)相对应的指针正确输入,并且如果到目前为止由该scanf()调用实际读取的字符数量可以在该类型的对象中表示,则实际计数应实际存储。否则,行为是未定义的。

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