【发布时间】:2021-03-01 21:40:15
【问题描述】:
最近发现的对 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
【问题讨论】:
-
FWIW,在正常解析期间首先调用
strlen而不是“只是”等待\0的原因是,如果你在其余的正常机器上搭载sscanf*scanf系列,明显的实现使用了一个基于扫描字符串的伪FILE对象。FILE对象通常包括计数和返回EOF的方法,这是其余代码所期望的。 (换句话说,寻找\0而不是EOF,或者懒洋洋地将\0变成EOF将是一个更复杂的返工。) -
@SteveSummit:我很清楚这一事实,在通用代码中对特殊情况
'\0'没有涉及返工。我在自己的实现中就是这样做的。'\0'不能是数字的一部分,因此无需更改数字解析器。其他解析器很容易适应。对任意长字符串调用strlen()是不可接受的。 -
@chqrlie 对浪费您的时间深表歉意;我明确地在我的评论前加上“FWIW”,以为所有人都不会感兴趣。
-
"sscanf 是否需要以空字符结尾的字符串作为输入?"和“
sscanf()是否应该读取超出转换完成所需字节的输入字符串?”是可能有不同答案的问题。 IMOsscanf需要一个以 null 结尾的字符串是完全可以的,但是在消耗任何字节之前寻找任意长字符串的末尾仍然是一个 QoI 问题。
标签: c scanf language-lawyer undefined-behavior