【问题标题】:Is std::stoi actually safe to use?std::stoi 实际上可以安全使用吗?
【发布时间】:2012-07-20 21:20:57
【问题描述】:

我与某人就std::stoi 的失败进行了愉快的交谈。说白了就是在内部使用std::strtol,如果报错就抛出。不过根据他们的说法,std::strtol 不应该为"abcxyz" 的输入报告错误,导致stoi 不会抛出std::invalid_argument。

首先,这里有两个程序在 GCC 上测试了这些情况的行为:
strtol
stoi

他们都在"123" 上显示成功,在"abc" 上显示失败。


我查看了标准以获取更多信息:

§ 21.5

Throws: invalid_argument if strtol, strtoul, strtoll, or strtoull reports that  
no conversion could be performed. Throws out_of_range if the converted value is  
outside the range of representable values for the return type.

这总结了依赖strtol 的行为。现在strtol 呢?我在 C11 草案中找到了这个:

§7.22.1.4

If the subject sequence is empty or does not have the expected form, no  
conversion is performed; the value of nptr is stored in the object  
pointed to by endptr, provided that endptr is not a null pointer.

鉴于传入"abc"的情况,C标准规定指向字符串开头的nptr将存储在传入的指针endptr中。这似乎与测试一致.此外,应返回 0,如下所述:

§7.22.1.4

If no conversion could be performed, zero is returned.

前面的引用说不​​会执行任何转换,所以它必须返回 0。这些条件现在符合 C++11 标准 stoi throwing std::invalid_argument。


这个结果对我来说很重要,因为我不想到处推荐 stoi 作为其他字符串到 int 转换方法的更好替代方法,或者自己使用它,就好像它按照您期望的方式工作一样,如果它没有将文本捕获为无效转换。

那么在这一切之后,我是不是哪里出错了?在我看来,我有很好的证据证明这个异常被抛出。我的证明是否有效,或者std::stoi 在给出"abc" 时不能保证抛出该异常?

【问题讨论】:

  • stoi 的问题在于它在"123abc" 上成功,这意味着除非您提供并检查结尾索引参数,否则它几乎没有用处。这使得它比strtol 更难使用,因为您需要检查结束索引并捕获异常。
  • @chris:我希望它会像boost::lexical_cast 那样抛出异常。最常见的用例是当您想要转换整个字符串时,stoi/l/ll 需要一堆样板代码来执行此操作(或将其包装在一个函数中,这也可以使用 strtol 完成)。 sto* 的实现方式更强大,但实际上并没有提供任何超过 strto* 的功能。
  • 顺便说一下,cin >>someInt 和cin >> someString; someInt = stoi (someString); 不一样。在输入123abc 的情况下,第一个版本将abc 留在流中,而第二个版本将其静默丢弃。
  • @interjay,是的,我明白你的意思,特别是如果这就是 boost 的作用。 “相同”是指整数的结果;我不关心比较中的流。
  • James Kanze 是个聪明人,但聪明人也会犯错。你的错误是你忘记在你的截图中检查他的名字。他的错误是他忘记了即使 abcdef 让 strtol 返回 0,strtol 也必须报告返回 0 的原因是转换失败,所以 stoi 必须抛出它的异常。但由于像 123abc 这样的情况,我仍然认为 stoi 不安全。

标签: c++ standards language-lawyer


【解决方案1】:

std::stoi 是否在输入 "abcxyz" 上抛出错误?

是的。

我认为您的困惑可能来自strtol 除了溢出之外从不报告错误这一事实。它可以报告未执行任何转换,但这在 C 标准中从未被称为错误条件。

strtol 在所有三个 C 标准中的定义都是类似的,我不再赘述无聊的细节,但它基本上定义了一个“主题序列”,它是对应于实际数字的输入字符串的子字符串。以下四个条件是等价的:

  • 主题序列具有预期的形式(简单的英语:它是一个数字)
  • 主题序列非空
  • 发生了转化
  • *endptr != nptr(仅当 endptr 不为空时才有意义)

当发生溢出时,仍称转换已发生。

现在很清楚,因为"abcxyz"不包含数字,所以字符串"abcxyz"的主题序列必须为空,这样就不能进行转换了。以下 C90/C99/C11 程序将通过实验确认:

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

int main() {
    char *nptr = "abcxyz", *endptr[1];
    strtol(nptr, endptr, 0);
    if (*endptr == nptr)
        printf("No conversion could be performed.\n");
    return 0;
}

这意味着std::stoi 的任何一致实现必须在输入"abcxyz" 时抛出invalid_argument 而没有可选的基本参数。


这是否意味着std::stoi 有令人满意的错误检查?

没有。当她说std::stoi 比在std::strtol 之后执行完整检查errno == 0 &amp;&amp; end != start &amp;&amp; *end=='\0' 更宽松时,您正在与之交谈的人是正确的,因为std::stoi 默默地删除了从第一个非数字字符开始的所有字符字符串。

事实上,在我的脑海中,唯一一种本机转换行为有点像std::stoi 的语言是 Javascript,即使这样你也必须使用 parseInt(n, 10) 强制以 10 为基数,以避免十六进制数字的特殊情况:

input      |  std::atoi       std::stoi      Javascript      full check 
===========+=============================================================
hello      |  0               error          error(NaN)      error      
0xygen     |  0               0              error(NaN)      error      
0x42       |  0               0              66              error      
42x0       |  42              42             42              error      
42         |  42              42             42              42         
-----------+-------------------------------------------------------------
languages  |  Perl, Ruby,     Javascript     Javascript      C#, Java,  
           |  PHP, C...       (base 10)                      Python...  

注意:不同语言在处理空格和多余的 + 符号方面也存在差异。


好的,所以我想要完整的错误检查,我应该使用什么?

我不知道有任何内置函数可以做到这一点,但boost::lexical_cast&lt;int&gt; 会做你想做的事。它特别严格,因为它甚至拒绝周围的空格,这与 Python 的 int() 函数不同。请注意,无效字符和溢出会导致相同的异常,boost::bad_lexical_cast。

#include <boost/lexical_cast.hpp>

int main() {
    std::string s = "42";
    try {
        int n = boost::lexical_cast<int>(s);
        std::cout << "n = " << n << std::endl;
    } catch (boost::bad_lexical_cast) {
        std::cout << "conversion failed" << std::endl;
    }
}

【讨论】:

  • javascript 实际上认为"0x42" == 66 是真的。
  • 您正在在您的答案中构建有趣的字符串,不是吗? :)
  • 为什么C语言的表中有std:atoi?我认为那应该是一个 C++..
  • @chris:欢迎来到 C/C++ 标准库的精彩世界:总是缺少一小块拼图 :-)
  • 您是否忘记了 std::stoi 有一个可选的第二个参数,该函数通过该参数报告输入字符串中无法转换的第一个字符的索引? IMO 这意味着该函数确实执行了完整的错误检查。
猜你喜欢
  • 2012-11-07
  • 2014-02-28
  • 2015-01-03
  • 1970-01-01
  • 1970-01-01
  • 2022-01-25
  • 2018-03-20
  • 2010-10-11
  • 2013-01-06
相关资源
最近更新 更多