【问题标题】:Type aware string to number conversion in C++在 C++ 中类型感知字符串到数字的转换
【发布时间】:2014-08-22 04:07:38
【问题描述】:

假设这个世界上所有的数字都是正整数,它们可以用 uintX_t C++ 类型来表示。

让我们考虑下一个将 std::string 转换为数字的很棒的代码:

#include <string>
#include <cstdint>
#include <iostream>

template <typename T>
T MyAwsomeConversionFunction(const std::string& value)
{
    T result = 0;
    for(auto it = value.begin(); it != value.end() && std::isdigit(*it); ++it)
    {
        result = result * 10 + *it - '0';
    }

    return result;
}

int main(int argc, const char * argv[])
{
    std::cout<<MyAwsomeConversionFunction<uint16_t>("1234")<<std::endl;
    std::cout<<MyAwsomeConversionFunction<uint16_t>("123456")<<std::endl;

    return 0;
}

如您所见,此函数存在多个错误,但我对特定的一个感兴趣:如何检测我的类型何时不足以包含该值(例如第二次转换调用)并避免 UB制作result = result * 10 + *it - '0';。我想知道该操作在进行之前是否会超过T 的最大值。这可能吗?

编辑:请查看Is signed integer overflow still undefined behavior in C++? 以获取有关 UB 关于 C++ 算术运算的更多信息。当结果溢出时,我想避免执行result = result * 10 + *it - '0'; 行。在答案中,该行仍在执行...

EDIT2:我在这里找到了答案:How to detect integer overflow?

EDIT3:接受的答案适用于签名类型。对于无符号类型 Cheers 和 hth。 - Alf 的答案是正确的。

【问题讨论】:

标签: c++ undefined-behavior numeric-conversion


【解决方案1】:

你只需要向后工作,询问给定的数字是否会溢出:

// When result exceeds this thresh, appending a digit will always overflow.
static const T thresh = std::numeric_limits<T>::max() / 10;
// When result equals this thresh, appending a digit larger than
// thresh_last_digit will overflow.
static const T thresh_last_digit = std::numeric_limits<T>::max() - 10 * thresh;

for(auto it = value.begin(); it != value.end() && std::isdigit(*it); ++it)
{
    if(result > threshold)
        throw std::overflow_error(value);
    T digit = *it - '0';
    if(result == threshold && digit > thresh_last_digit)
        throw std::overflow_error(value);
    result = result * 10 + digit;
}

【讨论】:

    【解决方案2】:

    尽管我可能会因为错误而被分道扬镳,但我会认真对待。这不处理字符串中的负值(您的原始代码也不处理)。正如 Alf 在对他的回答的评论中提到的那样,它仅限于 ASCII 数字。

    template <typename T>
    T MyAwsomeConversionFunction(const std::string& value)
    {
        T maxBeforeMult = std::numeric_limits<T>::max / 10;
        T result = 0;
        for(auto it = value.begin(); it != value.end() && std::isdigit(*it); ++it)
        {
            // Check if multiplying would overflow
            if (result > maxBeforeMult)
            {
                // throw overflow
            }
    
            result = result * 10;
            T digit = *it - 0;
    
            // Check if adding would overflow
            if (std::numeric_limits<T>::max - result < digit)
            {
                // throw overflow
            }
    
            result += digit;
        }
    
        return result;
    }
    

    【讨论】:

    • 这是不必要的低效,不必要的冗长和复杂,但我理解 OP 如何根据对 UB 的错误信念行事,将其投票为“解决方案”。就是这样。读者应该意识到这一点,以免他们选择这种方法。
    • @Cheersandhth.-Alf:随意提出(甚至编辑)改进建议。而且我不确定您所说的“对 UB 的错误信念”是什么意思,因为签名溢出似乎正是他所关心的(尽管在原始问题中没有明确说明)。
    • OP 反复断言,包括在问题本身中,类型只会是无符号的。一个有效且简单的有符号解决方案是将转换作为无符号进行,然后检查有符号限制。一般的解决方案是使用标准库。
    • 引用 C++ 标准第 5 节,表达式:“如果在计算表达式期间,结果未在数学上定义或不在其类型的可表示值范围内,则行为未定义。”
    • @Cheersandhth.-Alf:我看不出哪里有过这样的断言。您似乎已经从main() 推断出这一点,但进一步的编辑和 cmets 指的是签名溢出(例如关于签名溢出的问题的链接)。
    【解决方案3】:

    对于无符号类型T,您总是可以这样做

    T const original = result;
    result = result * 10 + *it - '0';
    if( result / 10 != original ) { throw 666; }
    

    除了将throw 666 替换为其他内容。


    关于使用溢出检测转换字符串→整数的明显原始问题,请参阅strtol 和家庭。

    【讨论】:

    • 如果T 是签名类型,这不就是UB吗?
    • @FredLarson: 不仅如此,如果模板是用浮点类型实例化的,/ 将不会进行假定的整数除法,如果它是用一些用户定义的类型实例化的自定义@ 987654328@,或者自定义+-*,它可以做任何事情。哎呀!
    • 可能是,但我认为 OP 没有考虑过此类问题。我并不完全认真。但是给定代码的一个真正问题是调用std::isdigit(*it)。适用于 ASCII(7 位)输入,但更普遍的是未定义行为。这就是微妙的问题。
    • @Felics:没有 UB。无符号算术以 2^N 为模执行,其中 N 是值表示中的位数,除了除以 0 外,它的定义完全明确。基于自己的无知和无能而匿名投票的人应该被鞭笞。跨度>
    • @Cheersandhth.-Alf 在详细阅读了有关整数溢出的 CERT 文档后,我发现在无符号整数方面我完全错了——它们的算术被很好地定义为模 2^ 位数。 UB 仅针对无符号整数触发。不幸的是,如果不对其进行编辑,我无法对您的答案进行投票。我还认为标准应该包含关于第 5 节的注释,表示对于不适用的无符号类型。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2015-10-04
    • 2012-02-15
    • 1970-01-01
    • 1970-01-01
    • 2019-05-30
    • 2011-03-09
    • 1970-01-01
    相关资源
    最近更新 更多