如何安全地在 unsigned int 和 int 之间进行 static_cast?

15

我有一个包含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 > MAX_INT,则该代码在我的编译器上无法工作(返回值为0)。 将val的类型更改为int也会对大数产生0结果。 我已经尝试了来自stackoverflow各种答案的不同解决方案,但仍未成功。

以下是我所知道的:

  • 我在OpenVMS上使用HP的C ++编译器(我相信使用Itanium处理器)。
  • sizeof(int)在我代码将运行的每种体系结构上至少为4。
  • 从一个大于INT_MAX的数字强制转换到int的行为因实现而异。 在我的机器上,它通常会得到0,但有趣的是,当将值从long转换为int时,结果为INT_MAX

这似乎非常难以正确执行,或者至少对我来说是这样。 有人知道一个可移植的解决方案吗?

更新:

static_cast更改为reinterpret_cast会导致编译器错误。 一条评论提示我尝试使用C风格的转换:return (int)val在上面的代码中,这起作用了。 在这台机器上。 在其他体系结构上是否仍然安全?


1
只是使用(int)val不行吗?然而,“将val的类型更改为int也会导致0...”这意味着问题可能来自于>>吗?(我真的不知道,我不使用C++ ;-) - user166390
有符号整数溢出不是实现定义的,而是未定义的。 - derobert
@derobert,感谢回复,我不太确定。我知道它不好。已相应更新了问题。 - Michael Kristofik
1
如果无符号数不在有符号类型的范围内,则将其从无符号转换为有符号是实现定义的。 - JohnPS
@derobert:这不是有符号溢出,而是整数转换,其结果是实现定义的。 - ildjarn
6个回答

15

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

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

由于结果具有实现定义性质,根据定义,不可能存在真正意义上的可移植解决方案。


13

虽然有使用类型转换和转换的方式可以实现这一点,但大多数方式依赖未定义的行为,这些行为在某些计算机/编译器上可能具有良好定义的行为。而不是依赖于未定义的行为,我们可以复制数据:

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

4
实现定义行为,而非未定义行为。 - ildjarn
2
@ildjarn:一个广泛使用的方法是 return *(int*)(&val);。这不是实现定义行为,而是未定义行为。 - David Hammen
1
啊,那相当于 reinterpret_cast,这确实是未定义行为;我以为你指的是 OP 问题中的 static_cast,其行为是实现定义的。 - ildjarn
为什么它是未定义行为? - Friedrich
1
@EmileCormier - 即使在最低优化级别下,调用memcpy也不会发生,因此它非常快;我使用多个编译器进行了测试。在除了最低优化级别以外的任何情况下,它都非常快,因为工作变量(我的答案中的signed_val)被优化掉了。我怀疑这些优化在我9年前写上述答案之前就已经存在了。即使是标准的原始版本,也肯定有as-if规则。这是一条规则,可以消除对memcpy的调用。 - David Hammen
显示剩余4条评论

5

你可以通过对一个无符号二进制补码数取反再加一来对其进行否定。因此,让我们对负数这样做:

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位二进制补码表示(或具有类似的范围)。它不依赖于任何与带符号整数溢出相关的未定义行为(请注意,无符号整数溢出的行为是明确定义的 - 尽管这里也不应该发生!)。

请注意,如果您的整数不是32位,则会变得更加复杂。您可能需要使用像~(~0U >> 1)这样的东西,而不是0x80000000。另外,如果您的整数不是二进制补码,则在某些值上可能会出现溢出问题(例如,在采用一补码机器时,无法用32位带符号整数表示-0x80000000)。然而,非二进制补码机器在今天非常罕见,所以这不太可能成为一个问题。


是的,我相信这段代码将来可能会在64位环境中运行。像这样硬编码比特模式可能不是一个好主意。不过这个解决方案在这台机器上是有效的。 - Michael Kristofik
大多数64位环境使用32位整数。无论如何,您可以使用“(unsigned yourinttype)0 >> 1)”来找到其他无符号整数类型(例如“unsigned long long”)的正确值。 - bdonlan

4
这是我用过的另一种解决方案:

以下是代码:

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

如果我屏蔽高位,转换时就可以避免溢出。然后再安全地进行OR操作。

4

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展示了一个如何通过memcpy实现自己的bit_cast例子(见Notes)。

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


值得注意的是,memcpy 方法与此答案匹配 https://dev59.com/H2sz5IYBdhLWcg3w3r4K#7602036 - Michael Kristofik

-2
unsigned int u = ~0U;
int s = *reinterpret_cast<int*>(&u); // -1

相反地:

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

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接