基础不在[2,36](GCC)时C ++ 11 std :: stoi默默失败

时间:2014-07-01 07:03:34

标签: c++ gcc c++11 std

我在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指定的。)

1 个答案:

答案 0 :(得分:5)

  

[C++11: 21.5/3]:如果strtolstrtoulstrtollstrtoull报告无法执行转换,则会抛出: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和标准中的错误。