【问题标题】:How does one safely static_cast between unsigned int and int?如何在 unsigned int 和 int 之间安全地进行静态转换?
【发布时间】:2011-09-29 18:25:30
【问题描述】:

我有一个 8 字符的 string 代表十六进制数字,我需要将其转换为 int。此转换必须保留字符串 "80000000" 及更高版本的位模式,即这些数字应为负数。不幸的是,天真的解决方案:

int hex_str_to_int(const string hexStr)
{    
    stringstream strm;
    strm << hex << hexStr;
    unsigned int val = 0;
    strm >> val;
    return static_cast<int>(val);
}

如果val &gt; MAX_INT(返回值为0),则不适用于我的编译器。将 val 的类型更改为 int 也会导致较大的数字为 0。我已经从这里的各种答案中尝试了几种不同的解决方案,但还没有成功。

以下是我所知道的:

  • 我在 OpenVMS 上使用 HP 的 C++ 编译器(我相信使用的是安腾处理器)。
  • sizeof(int) 在我的代码将运行的每个架构上至少有 4 个。
  • 从数字 > INT_MAX 转换为 int 是实现定义的。在我的机器上,它通常会导致 0,但有趣的是,当值太大时,从 long 转换为 int 会导致 INT_MAX

要正确地做到这一点非常困难,或者至少对我来说是这样。有谁知道对此的便携解决方案?

更新:

static_cast 更改为reinterpret_cast 会导致编译器错误。一条评论提示我在上面的代码中尝试使用 C 风格的演员表:return (int)val,它成功了。 在这台机器上。在其他架构上仍然安全吗?

【问题讨论】:

  • 不能只使用(int)val吗?但是,“将 val 的类型更改为 int 也会导致 0....”意味着问题可能来自 &gt;&gt;? (我真的不知道,我不使用 C++ ;-)
  • 有符号整数溢出不是实现定义的,它是未定义的。
  • @derobert,谢谢,我不确定。我知道这不好。相应地更新了问题。
  • 如果无符号数不在有符号类型的范围内,则从无符号转换为有符号是实现定义的。
  • @derobert : 这不是有符号溢出,这是整数转换,其结果 实现定义的。

标签: c++ casting integer-overflow openvms


【解决方案1】:

引用 C++03 标准,§4.7/3(积分转换):

如果目标类型是有符号的,如果它可以在目标类型(和位域宽度)中表示,则值不变; 否则,该值由实现定义

因为结果是实现定义的,所以根据定义不可能有一个真正的可移植解决方案。

【讨论】:

    【解决方案2】:

    虽然有一些方法可以使用强制转换和转换来做到这一点,但大多数都依赖于未定义的行为,这些行为恰好在某些机器上/使用某些编译器时具有明确定义的行为。不要依赖未定义的行为,而是复制数据:

    int signed_val;
    std::memcpy (&signed_val, &val, sizeof(int));
    return signed_val;
    

    【讨论】:

    • 实现定义的行为,不是未定义的。
    • @ildjarn:一种广泛使用的方法是return *(int*)(&amp;val); 这不是实现定义的行为。这是未定义的行为。
    • 啊,相当于一个reinterpret_cast,确实是UB;我假设您指的是 OP 问题中的 static_cast,其行为是实现定义的。
    • 为什么是未定义的行为?
    • @EmileCormier - 即使在最低优化级别也相当快,因为​​不会调用memcpy;我用多个编译器测试过。除了最低优化级别之外,它的速度非常快,因为工作变量(我的回答中的signed_val)被优化掉了。我怀疑这些优化在我 9 多年前写上述答案之前就已经到位。即使在标准的原始版本中,as-if 规则也确实存在。这是消除对memcpy 的调用的规则。
    【解决方案3】:

    您可以通过取补加一来否定无符号二进制补码数。所以让我们为否定做这件事:

    if (val < 0x80000000) // positive values need no conversion
      return val;
    if (val == 0x80000000) // Complement-and-addition will overflow, so special case this
      return -0x80000000; // aka INT_MIN
    else
      return -(int)(~val + 1);
    

    这假设您的整数用 32 位二进制补码表示(或具有相似范围)表示。它不依赖于任何与有符号整数溢出相关的未定义行为(请注意,unsigned 整数溢出的行为是明确定义的——尽管这也不应该发生在这里!)。

    请注意,如果您的 int 不是 32 位的,事情会变得更加复杂。您可能需要使用类似~(~0U &gt;&gt; 1) 而不是0x80000000。此外,如果您的整数不是二进制补码,则某些值可能会出现溢出问题(例如,在二进制补码机器上,-0x80000000 不能用 32 位有符号整数表示)。但是,非二进制补码机在今天非常罕见,因此这不太可能成为问题。

    【讨论】:

    • 是的,我很确定这段代码有一天可能会在 64 位环境中运行。像这样硬编码位模式可能不是一个好主意。不过,此解决方案适用于这台机器。
    • 大多数 64 位环境使用 32 位整数。不过,无论如何,您都可以使用~(~(unsigned yourinttype)0 &gt;&gt; 1) 为其他无符号整数类型找到正确的值(例如,unsigned long long
    【解决方案4】:

    这是另一个对我有用的解决方案:

    if (val <= INT_MAX) {
        return static_cast<int>(val);
    }
    else {
        int ret = static_cast<int>(val & ~INT_MIN);
        return ret | INT_MIN;
    }
    

    如果我屏蔽掉高位,我会在投射时避免溢出。然后我可以安全地返回它。

    【讨论】:

      【解决方案5】:

      C++20 将有 std::bit_cast 逐字复制位:

      #include <bit>
      #include <cassert>
      #include <iostream>
      
      int main()
      {
          int i = -42;
          auto u = std::bit_cast<unsigned>(i);
          // Prints 4294967254 on two's compliment platforms where int is 32 bits
          std::cout << u << "\n";
      
          auto roundtripped = std::bit_cast<int>(u);
          assert(roundtripped == i);
          std::cout << roundtripped << "\n"; // Prints -42
      
          return 0;
      }
      

      cppreference 显示了一个example,说明如何根据memcpy(在注释下)实现自己的bit_cast

      虽然 OpenVMS 不太可能很快获得 C++20 支持,但我希望这个答案可以帮助通过互联网搜索找到相同问题的人。

      【讨论】:

      【解决方案6】:
      unsigned int u = ~0U;
      int s = *reinterpret_cast<int*>(&u); // -1
      

      逆向:

      int s = -1;
      unsigned int u = *reinterpret_cast<unsigned int*>(&s); // all ones
      

      【讨论】:

        猜你喜欢
        • 2014-08-03
        • 1970-01-01
        • 2021-11-22
        • 1970-01-01
        • 2013-02-02
        • 1970-01-01
        • 2014-04-05
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多