sscanf是否需要以空字符结尾的字符串作为输入?
最近发现的对 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;
}
函数应该返回1并且不需要从 中读取多个字节buf,但sscanf()允许从buf第一个字节之外读取?
(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
sscanf函数概要概要
#include <stdio.h> int sscanf(const char * restrict s, const char * restrict format, ...);说明
该sscanf函数等效于fscanf,不同之处在于输入是从字符串(由参数指定s)而不是从流中获得的。到达字符串的末尾相当于遇到fscanf函数的文件尾。如果复制发生在重叠的对象之间,则行为未定义。返回如果在第一次转换(如果有)完成之前发生输入失败,
该sscanf函数将返回宏的值EOF。否则,该sscanf函数返回分配的输入项的数量,如果早期匹配失败,该数量可能少于提供的数量,甚至为零。
输入专门称为字符串,因此它应该以空字符结尾
尽管字符串中匹配转换说明符的初始前缀之外的字符以及可能帮助确定匹配序列结尾的下一个字节都没有用于转换,但这些字符后面必须跟一个空终止符,因此输入是一个格式良好的字符串,它符合调用strlen()它来确定输入长度。
为避免长输入字符串的线性时间复杂度,sscanf()应将字符串末尾的扫描限制为具有strnlen()或等效的小尺寸,并通过适当的重新填充函数。传递一个巨大的长度并让内部例程特殊情况下的空字节是一种更好的方法。
同时,程序员应该避免将长输入字符串传递sscanf()给他们的解析任务并使用更专业的函数,例如strtol(),这也需要格式良好的 C 字符串,但以更保守的方式实现。这也将避免超出范围字符串表示的数字转换的潜在未定义行为。