我在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
也就是说,它解析了&#34; 100&#34;在基数2中作为数字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: %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
是&#34;垃圾&#34; value:这是因为strtol()
使endptr
保持不变,并且C ++包装器错误地从中减去输入字符串起始地址。
在C版本中,我们还看到errno
设置为EINVAL
以指示错误。我的系统上的文档说明当base
无效时会发生这种情况,但也说C99没有指定它。如果我们在C ++版本中打印errno
,我们也可以检测到这个错误(但它在C99中不是标准的,并且它确定不是由C ++ 11指定的。)
答案 0 :(得分:5)
[C++11: 21.5/3]:
如果strtol
,strtoul
,strtoll
或strtoull
报告无法执行转换,则会抛出:invalid_argument。 [..]
[C99: 7.20.1.4/5]:
如果主题序列具有预期形式且base
的值为零,则根据6.4的规则将以第一个数字开头的字符序列解释为整数常量。 4.1。如果主题序列具有预期形式并且base
的值在2和36之间,则将其用作转换的基础,将其值归于每个字母,如上所述。 [..]
对于base
不零或介于2和36之间的情况,C99中未指定语义,因此结果未定义。这不一定满足[C++11: 21.5/3]
的摘录。
简而言之,这就是UB;只有在基数有效但输入值在该基数中不可转换时,才会出现异常。 这是GCC和标准中的错误。