sscanf 是否需要以空字符结尾的字符串作为输入?

Does sscanf require a null terminated string as input?

最近发现的关于 GTA 加载时间过长的解释(1) 表明 sscanf() 的许多实现在其输入字符串上调用 strlen() 来设置与其他扫描功能共享的内部例程的上下文对象(scanf()fscanf()...)。当输入字符串很长时,这可能成为性能瓶颈。解析作为字符串加载的 10MB JSON 文件并重复调用带有偏移量的 sscanf()%n 转换被证明是加载时间的主要原因。

我的问题是 sscanf() 是否应该读取超过完成转换所需字节的输入字符串?例如,以下代码是否会调用未定义的行为:

int test(void) {
    char buf[1] = { '1' };
    int v;
    sscanf(buf, "%1d", &v);
    return v;
}

该函数应该 return 1 并且不需要从 buf 读取超过一个字节,但是 sscanf() 允许从 [=19= 读取] 超出第一个字节?


(1) JdeBP提供的参考资料:
https://nee.lv/2021/02/28/How-I-cut-GTA-Online-loading-times-by-70/
https://news.ycombinator.com/item?id=26297612
https://github.com/biojppm/rapidyaml/issues/40

以下是 C 标准的相关部分:

7.21.6.7 The sscanf function Synopsis

Synopsis

#include <stdio.h>
int sscanf(const char * restrict s, const char * restrict format, ...);

Description
The sscanf function is equivalent to fscanf, except that input is obtained from a string (specified by the argument s) rather than from a stream. Reaching the end of the string is equivalent to encountering end-of-file for the fscanf function. If copying takes place between objects that overlap, the behavior is undefined.

Returns
The sscanf function returns the value of the macro EOF if an input failure occurs before the first conversion (if any) has completed. Otherwise, the sscanf function returns the number of input items assigned, which can be fewer than provided for, or even zero, in the event of an early matching failure.

输入具体指的是一个字符串,所以它应该以null结尾

尽管字符串中超出与转换说明符匹配的初始前缀之外的 none 个字符以及可能有助于确定匹配序列结尾的下一个字节用于转换,但这些字符必须是后跟一个空终止符,因此输入是一个格式正确的字符串,并且符合对其调用 strlen() 以确定输入长度。

为了避免长输入字符串的线性时间复杂度,sscanf() 应该使用 strnlen() 或等价物将对字符串结尾的扫描限制为较小的大小,并传递适当的重新填充函数。传递一个巨大的长度并让内部例程特殊情况为空字节是一个更好的方法。

与此同时,程序员应避免将长输入字符串传递给 sscanf() 并使用更专门的函数来完成其解析任务,例如 strtol(),这也需要格式正确的 C 字符串,但以更保守的方式实施。这也将避免在超出范围的字符串表示形式的数字转换中出现潜在的未定义行为。

编写标准时,几乎所有现有实现都以相同方式处理许多库函数,但某些实现可能有充分的理由以不同方式处理少数情况。如果有理由与普通行为不同的实现数量很多,那么委员会要么要求所有实现以普通方式运行(例如计算 UINT_MAX+1u 时发生的情况),要么明确声明它们不需要这样做(例如计算 INT_MAX+1 时)。如果存在明显的共同行为,但它可能不适用于所有实现,然而,委员会通常只是简单地避免说任何话,假设大多数编译器没有理由偏离共同行为,并且那些有理由偏离的作者比委员会更有资格判断遵循共同行为与偏离共同行为的利弊。

所讨论的 sscanf 行为符合后一种模式。委员会不想强制要求必须更改如果数据源没有尾随零字节就会出现问题的实现来处理此类数据源,但他们也不想要求程序员从那些数据源复制数据在对它使用 sscanf 之前,不要有尾随零字节到那些地方,即使它们的实现不关心超出将被有意义地检查的源部分之外的任何东西。由于需要尾随零的实现的制造商可能会阻止对标准的任何更改,这将要求他们容忍它的缺失,而其实现不强加此类不必要要求的程序员将阻止对标准的任何更改,这些更改需要他们添加额外的将数据复制步骤复制到他们的代码中,情况将保持僵局,除非人们同意将强加尾随字节要求的实现归类为“符合但有缺陷”,并要求他们通过预定义的宏或其他此类方式指示此类缺陷。