为什么我不能将uint重新解释为int?

25
这是我想要做的事情:
```const int64_t randomIntNumber = reinterpret_cast(randomUintNumber);```
其中randomUintNumber的类型为uint64_t。
错误信息为(MSVC 2010):
``` error C2440: 'reinterpret_cast' : cannot convert from 'const uint64_t' to 'int64_t' 1> Conversion is a valid standard conversion, which can be performed implicitly or by use of static_cast, C-style cast or function-style cast ```
为什么编译不通过?两种类型具有相同的位数长度,这不是reinterpret_cast的预期吗?

如果您能列出您得到的错误,我们可能更容易地说出它为什么不能编译。 - Some programmer dude
@JoachimPileborg:我的错,我扩展了问题。 - Violet Giraffe
1
老帖子我知道,C++20的std::bit头文件中的std::bit_cast正好可以做到这一点:> - undefined
@AnthoFoxo:谢谢!我经常使用自己的memory_cast<>,它使用memcpy来正确地使用指定数据的位表示创建一个新对象,但我完全忘记了bit_cast的存在。 - undefined
7个回答

33

因为这不是reinterpret_cast的用途。使用reinterpret_cast进行允许的所有转换都涉及指针或引用,唯一的例外是整数或枚举类型可以reinterpret_cast成自身。这些都在标准中定义,[expr.reinterpret.cast]

我不确定你在这里想要实现什么,但如果你希望randomIntNumber具有与randomUintNumber相同的值,则请执行以下操作:

const int64_t randomIntNumber = randomUintNumber;
如果这导致编译器警告或者您想要更加明确,那么:
const int64_t randomIntNumber = static_cast<int64_t>(randomUintNumber);

如果randomUintNumber小于263,则类型转换的结果与输入值相同。否则,结果是实现定义的,但我预计所有已知具有int64_t的实现都将定义它执行明显的操作:结果等效于对264取模的输入。


如果你想让randomIntNumber具有与randomUintNumber相同的比特模式,则可以这样做:

int64_t tmp;
std::memcpy(&tmp, &randomUintNumber, sizeof(tmp));
const int64_t randomIntNumber = tmp;

由于 int64_t 保证使用二进制补码表示,你会希望实现定义 static_cast 在对于 uint64_t 的超出范围值时具有相同的结果。但据我所知,这在标准中并不是保证的。

即使 randomUintNumber 是编译时常量,不幸的是,这里的 randomIntNumber 并不是编译时常量。但那么,编译时常量有多“随机”呢? ;-)

如果需要解决这个问题,并且您不信任实现将无符号值转换为有符号类型,那么可以采用以下方法:

const int64_t randomIntNumber = 
    randomUintNumber <= INT64_MAX ? 
        (int64_t) randomUintNumber :
        (int64_t) (randomUintNumber - INT64_MAX - 1) + INT64_MIN;

现在,我倾向于编写尽可能真正可移植的代码,但即使如此,我认为这接近于偏执狂。


顺便说一句,你可能会想这样写:

const int64_t randomIntNumber = reinterpret_cast<int64_t&>(randomUintNumber);

或者等价地:

const int64_t randomIntNumber = *reinterpret_cast<int64_t*>(&randomUintNumber);

这并不完全保证可行,因为虽然存在int64_tuint64_t,它们保证是相同大小的有符号类型和无符号类型,但它们实际上并不保证是一个标准整数类型的有符号和无符号版本。因此,这段代码是否违反了严格别名规则取决于具体实现。违反严格别名规则的代码将产生未定义行为。以下代码不会违反严格别名规则,并且只要randomUintNumber中的位模式是long long值的有效表示,就可以使用:

unsigned long long x = 0;
const long long y = reinterpret_cast<long long &>(x);

因此,在int64_tuint64_t被定义为long longunsigned long long的实现中,我的reinterpret_cast是可以的。就像将超出范围的值转换为有符号类型一样,您会期望实现的明智之举是将它们对应为有符号/无符号类型。因此,像static_cast和隐式转换一样,您希望它在任何明智的实现中都能工作,但实际上并不保证。


(吹毛求疵)据我所知,int64_t使用二进制补码表示的保证是针对C++20及以上版本的。 - Zejj
@Zejj:现在已经过去很久了,但我记得int64_t一直要求是二进制补码。如果一个实现有一个64位有符号整数类型,但不是二进制补码,比如long long之类的,那么就不能把它直接用作int64_t(u)intX_t类型还要求没有填充位,可能还有其他一些限制。但这只是我凭记忆说的,我不确定C和C++之间是否有差异。 - undefined
我也记不清楚int64_tuint64_t是否必须具有相同的字节顺序,或者不需要!显然,如果它们没有相同的字节顺序,那将是很奇怪的。 - undefined

7

在这些情况下,请使用static_cast。我猜测,语言设计者们在他们的智慧中决定,它不被认为是足够"不安全",不需要使用reinterpret_cast


3
不是的,reinterpret_cast 主要用于将现有的存储位重新解释为不同于其本身类型的类型。很多这样的解释都依赖于具体实现,标准列出了一个具体(太长了,无法在此引用)的 reinterpret_cast 可以执行的操作列表(主要是在不同的指针/引用类型之间进行转换),但是标准也指出:

不能使用 reinterpret_cast 显式地执行任何其他类型的转换。

在您的情况下,您可能需要进行类型转换,而不是重新解释现有存储。为此,请使用 static_cast

2
但重新解释正是我想要的!我不确定 static_cast(或C-cast)是否会产生所需的结果,必须先刷新一下它的工作原理。 - Violet Giraffe
1
@VioletGiraffe:如果你想将某个存储重新解释为不同的类型,那么你应该重新解释转换指向该存储的指针。但要注意,如果您对结果进行解引用,您将进入IB/UB领域。 - PlasmaHH
我怎么会忘记...这正是我上次遇到这个问题时解决它的方法(将指针转换为目标类型并对其进行取消引用)。IB/UB在这里具体表现在哪里? - Violet Giraffe
@VioletGiraffe:潜在的UB是由于严格别名,详见我的回答。 - Steve Jessop

3

使用memcpy重新解释位模式,使用static_cast转换类型

使用reinterpret_cast会违反strict aliasing规则。 这是不好的,因为它会导致未定义的行为(也许您的代码在新的编译器版本中出现故障)。 什么是Strict Aliasing规则,我们为什么要关注它?是一篇很棒的文章,描述了问题及其解决方案(也许可以跳过冗长的“现在,来看规则书”部分;))。它推荐使用memcpy并认为编译器优化将跳过复制操作。

代码

交互式代码在此处。在您的特定情况下,所有选项都产生相同的结果。但是当玩弄newTyperandomUintNumber时,情况就会改变。

#include <iostream>
#include <cstring>

int main()
{
    typedef int64_t newType; // try: double, int64_t
    
    uint64_t randomUintNumber = INT64_MAX + 10000; // try: 64000, INT64_MIN, INT64_MAX, UINT64_MAX, INT64_MAX + 10000
    std::cout << "INT64_MIN: " << INT64_MIN << std::endl;
    std::cout << "UINT64_MAX: " << UINT64_MAX << std::endl;
    std::cout << "INT64_MAX: " << INT64_MAX << "\n\n";
    std::cout << "randomUintNumber: " << randomUintNumber << "\n\n";
    
    // const int64_t randomIntNumber = reinterpret_cast<int64_t> (randomUintNumber);
    std::cout << "as \"const int64_t randomIntNumber = reinterpret_cast<int64_t> (randomUintNumber);\" does not work, here are some options ...\n\n";
    
    std::cout << "BAD [undefined behavior!]:" << std::endl;
    const newType a = reinterpret_cast<int64_t&> (randomUintNumber);
    std::cout << "\treinterpret_cast<int64_t&> (randomUintNumber): " << a << std::endl;
    
    const newType b = reinterpret_cast<int64_t&&> (randomUintNumber);
    std::cout << "\treinterpret_cast<int64_t&&> (randomUintNumber): " << b << std::endl;
    
    std::cout << "\nGOOD: " << std::endl;
    const newType c = (int64_t) randomUintNumber;
    std::cout << "\t(int64_t) randomUintNumber [static cast, sometimes reinterprets bit pattern]: " << c << std::endl;
    
    const newType d = static_cast<int64_t>(randomUintNumber);
    std::cout << "\tstatic_cast<int64_t>(randomUintNumber) [the same as before]: " << d << std::endl;
    
    static_assert(sizeof(uint64_t) == sizeof(newType), "should not be taken for granted ...");
    newType eNotConst;
    std::memcpy(&eNotConst, &randomUintNumber, sizeof(uint64_t));
    const newType e = eNotConst;
    std::cout << "\tstd::memcpy(&eNotConst, &randomUintNumber, sizeof(uint64_t)); [definately reinterprets bit pattern]: " << e << std::endl;
    
    
    return 0;
}

输出

INT64_MIN: -9223372036854775808
UINT64_MAX: 18446744073709551615
INT64_MAX: 9223372036854775807

randomUintNumber: 9223372036854785807

as "const int64_t randomIntNumber = reinterpret_cast<int64_t> (randomUintNumber);" does not work, here are some options ...

BAD [undefined behavior!]:
    reinterpret_cast<int64_t&> (randomUintNumber): -9223372036854765809
    reinterpret_cast<int64_t&&> (randomUintNumber): -9223372036854765809

GOOD: 
    (int64_t) randomUintNumber [static cast, sometimes reinterprets bit pattern]: -9223372036854765809
    static_cast<int64_t>(randomUintNumber) [the same as before]: -9223372036854765809
    std::memcpy(&eNotConst, &randomUintNumber, sizeof(uint64_t)); [definately reinterprets bit pattern]: -9223372036854765809

2

根据C++11标准(N3376)5.2.10.1此文档,第101页

以下是可以明确使用reinterpret_cast执行的转换。没有其他转换可以使用reinterpret_cast明确执行。

唯一允许的从整数类型到整数类型的转换实际上是如果类型相同。

其他涉及指针。


1

reinterpret_cast用于将对象的存储解释为不同的对象。如果您不想使用标准措辞,可以在这里找到所有reinterpret_cast可以执行的操作。通常,您需要使用指针类型。

因此,如果您真的想将用于uint64_t的位重新解释为int64_t,则应执行以下操作:

int64_t randomIntNumber = reinterpret_cast<int64_t&> (randomUintNumber);

然而,如果您只想转换对象并尽可能地保留其值...只需按照编译器的建议使用static_cast即可。


1

为什么它不能编译?

因为两种类型都不是指针。

两种类型具有相同的位长度,

那有什么关系呢?如果它们是指针,也许它们指向相同大小的东西很重要,但它们不是指针。

这不是reinterpret_cast的预期吗?

不是,reinterpret_cast是用于指针转换的。您可以在转换中放置一个&,并在外部放置一个*来实现您想要的操作。这就是reinterpret_cast的用途。


位长度很重要,因为如果您尝试按位重新解释不同长度的类型,显然会遇到问题。 - Violet Giraffe
这是关于reinterpret_cast的一个常见误解。从概念上讲,它并不执行位重新解释(尽管在常见平台上“碰巧”如此)。例如,C++标准(5.2.10)指出,使用reinterpret_cast,可以将“指针显式转换为足以容纳它的任何整数类型。”——其中包括更大的整数类型。而且,它明确表示“可能会产生与原始值不同的表示形式”,也可能不会。 - David Schwartz
@VioletGiraffe 当使用reinterpret_cast的结果时,指向对象或引用对象的位长度很重要。指针本身的位长度是无关紧要的:可以在char*int*之间进行reinterpret_cast,即使char*int*具有不同的大小(就像在旧机器上有时会出现这种情况)。 - James Kanze

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