c++ 11 std::stoi在base不在[2,36](GCC)时静默失败
C++11 std::stoi silently fails when base not in [2,36] (GCC)
我在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
,但不完全(相差几百万)。如果我在没有优化的情况下编译,那么pos
是18446744073703251929
,所以它看起来像未初始化的垃圾,尽管我确实初始化了它(为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()
vs atoi()
的目标之一是更好的错误检测,但似乎根本没有检查base
。
编辑:这是同一个程序的C版本,也打印errno:
#include <stdio.h>
#include <stdlib.h>
int main(int argc, char* argv[])
{
char* pos = (char*)42;
printf("result: %ldn", 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
现在我们看到为什么pos
在c++版本中是一个"垃圾"值:这是因为strtol()
使endptr
保持不变,而c++包装器错误地从中减去了输入字符串的起始地址。
在C版本中,我们还看到errno
被设置为EINVAL
来指示错误。我系统上的文档说,当base
无效时会发生这种情况,但也说它不是由C99指定的。如果我们在c++版本中打印errno
,我们也可以检测到这个错误(但它不是C99的标准,并且肯定没有在c++ 11中指定)。
[C++11: 21.5/3]:
抛出:invalid_argument如果strtol
,strtoul
,strtoll
,或strtoull
报告不能进行转换。 [. .]
[C99: 7.20.1.4/5]:
如果主题序列符合预期的形式,且base
的值为零,则根据6.4.4.1的规则将从第一个数字开始的字符序列解释为整数常数。如果主题序列具有预期的形式,并且base
的值在2到36之间,则将其作为转换的基数,将每个字母的值赋给上面给出的值。 [. .]
当base
不是 0或在2到36之间时,C99中没有指定语义,因此结果是未定义的。这并不一定满足[C++11: 21.5/3]
的摘录。
简而言之,这是UB;只有当基类型有效,但输入值在该基类型中不可转换时,才会出现异常。
- gcc和c++17的过载解析失败
- 使用 GCC 卸载的 OpenMP 卸载失败,并出现"Ptx assembly aborted due to errors"
- 自定义对象的dlib序列化在gcc中失败
- GCC 4.8.2 自动矢量化由于 cout 而失败
- 调试模板时出现问题.专门针对 Linux GCC 7、GCC 6、GCC 5、GCC 4.9 错误构建失败:模板参数 1
- C++正则表达式失败(GCC vs Microsoft 编译器)
- 带有引用的std::tuple在clang中编译失败,但在gcc中编译失败
- thread_local静态成员模板定义:初始化失败,GCC
- C++指向成员的指针的类内初始化会使 MSVC 失败(但 GCC/Clang 工作)
- 在 GCC 中工作的外行构造函数模板在 Clang 中失败
- enable_if is_same constexpr函数使MSVC失败(但在Clang,GCC中效果很好)
- GCC 模板参数推断/替换失败
- 为什么这段代码在Visual Studio中有效,但在gcc中失败
- 为什么将 lambda 用于非类型模板参数时 gcc 失败?
- 匿名结构可提高函数转换在GCC 5.4上失败
- 线性重载:为什么 clang 在 GCC 编译时失败?
- enable_if的模板专用化在 Clang 中失败,适用于 GCC
- 错误:命令"GCC"失败,退出状态为 1,正在安装 PyMix
- c++ 中带有容器迭代器的循环类型依赖关系(GCC 失败,而 MSVC 正常)
- 如何修复错误:命令'x86_64-linux-gnu-gcc'失败,退出状态为 1