gpt4 book ai didi

当 base 不在 [2,36] (GCC) 中时,C++11 std::stoi 静默失败

转载 作者:IT老高 更新时间:2023-10-28 23:20:57 28 4
gpt4 key购买 nike

我在 Linux 上使用 GCC 4.9.0。这是我的测试程序:

#include <iostream>
#include <string>

using namespace std;

int main(int argc, char* argv[])
{
size_t pos = 42;
cout << "result: " << stoi(argv[1], &pos, atoi(argv[2])) << '\n';
cout << "consumed: " << pos << '\n';
}

这是一个预期的结果:

$ ./a.out 100 2
result: 4
consumed: 3

也就是说,它将以 2 为底的“100”解析为数字 4,并消耗了所有 3 个字符。

我们可以在 36 以内进行类似操作:

 $ ./a.out 100 36
result: 1296
consumed: 3

但是更大的基地呢?

$ ./a.out 100 37
result: 0
consumed: 18446744073707449552

这是什么? pos 应该是它停止解析的索引。这里它接近 std::string::npos 但并不完全(相差几百万)。如果我在没有优化的情况下编译,那么 pos18446744073703251929 ,所以它看起来像未初始化的垃圾,尽管我确实初始化了它(到 42)。事实上,valgrind 提示道:

Conditional jump or move depends on uninitialised value(s)
at 0x400F11: int __gnu_cxx::__stoa<long, int, char, int>(...) (in a.out)
by 0x400EC7: std::stoi(std::string const&, unsigned long*, int) (in a.out)

所以这很有趣。此外,std::stoi 的文档说,如果无法执行转换,它会抛出 std::invalid_argument。显然这种情况下它没有进行任何转换,它在pos中返回了垃圾,并且没有抛出异常。

如果 base 为 1 或负数,也会发生类似的坏事。

这是 GCC 实现中的错误、标准中的错误,还是我们必须学会忍受的东西?我认为 stoi()atoi() 的目标之一是更好的错误检测,但似乎根本不检查 base


编辑:这是同一程序的 C 版本,它也打印 errno:

#include <stdio.h>
#include <stdlib.h>

int main(int argc, char* argv[])
{
char* pos = (char*)42;
printf("result: %ld\n", strtol(argv[1], &pos, atoi(argv[2])));
printf("consumed: %lu (%p)\n", pos - argv[1], pos);
perror("errno");
return 0;
}

当它工作时,它会做和以前一样的事情。当它失败时,它会更加清晰:

$ ./a.out 100 37
result: 0
consumed: 18446603340345143502 (0x2a)
errno: Invalid argument

现在我们明白了为什么 C++ 版本中的 pos 是一个“垃圾”值:这是因为 strtol() 没有改变 endptr,并且 C++ 包装器错误地从中减去输入字符串的起始地址。

在 C 版本中,我们还看到 errno 设置为 EINVAL 以指示错误。我系统上的文档说当 base 无效时会发生这种情况,但也说它不是 C99 指定的。如果我们在 C++ 版本中打印 errno 我们也可以检测到这个错误(但它在 C99 中不是标准的,并且肯定不是 C++11 指定的)。

最佳答案

[C++11: 21.5/3]: Throws: invalid_argument if strtol, strtoul, strtoll, or strtoull reports that no conversion could be performed. [..]

[C99: 7.20.1.4/5]: If the subject sequence has the expected form and the value of base is zero, the sequence of characters starting with the first digit is interpreted as an integer constant according to the rules of 6.4.4.1. If the subject sequence has the expected form and the value of base is between 2 and 36, it is used as the base for conversion, ascribing to each letter its value as given above. [..]

C99 中没有为 basenot 0 或介于 2 和 36 之间的情况指定语义,因此结果未定义。这不一定满足 [C++11: 21.5/3] 的摘录。

简而言之,这是 UB;只有当基数有效但输入值在该基数中不可转换时,您才会期望出现异常。 这既不是 GCC 也不是标准中的错误。

关于当 base 不在 [2,36] (GCC) 中时,C++11 std::stoi 静默失败,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/24504269/

28 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com