reinterpret_cast大多数情况下是无用的吗?

21

我已经阅读了关于使用 reinterpret_cast 的各种之前的问题,并且也阅读了相关的 C++ 标准措辞。本质上,问题在于指针转换操作的 reinterpret_cast 结果不能安全地用于除了强制转换回原始指针类型之外的任何其他目的。

然而,在实践中,大多数对 reinterpret_cast 的真实世界用途似乎都基于(错误的)假设,即 reinterpret_cast 与 C 风格转换相同。例如,我看过很多代码使用 reinterpret_castchar* 转换为 unsigned char*,以进行字符集转换函数。这完全无害,但严格来说不可移植 - 没有保证从 char*unsigned char*reinterpret_cast 在您尝试解引用 unsigned char* 指针时不会崩溃程序。

根据标准,reinterpret_cast 仅有唯一另一个具有任何真实保证的用途是将指针转换为整数,反之亦然。

然而,有很多情况下我们希望(并且应该能够)在不同的指针类型之间进行安全转换。例如:uint16_t* 到 新的 C++0x char16_t*,或者基本数据类型的任何指针,其大小/对齐方式与原始类型相同。但是,reinterpret_cast 并不保证这应该起作用。

问题:我们如何安全地在指针之间进行转换,这些指针指向相同大小/对齐方式的基本数据类型,比如char* --> unsigned char*?由于reinterpret_cast似乎不能保证实际有效,C风格的转换是唯一安全的选择吗?


7
C语言风格的强制类型转换也不能保证始终有效。 - sbi
3
在C++中,C风格的类型转换被明确定义为static_cast、const_cast和reinterpret_cast。除了C风格转换可以忽略访问权限之外,这些转换的行为是精确定义的。 - Thomas Edleson
4
严格来说,“bit-for-bit” 并不是精确的术语,更准确的说法应该是“实现定义”,这个定义理论上可以有很多种可能性。同时,强制使用“bit-for-bit” 来进行指针强制类型转换也不一定总是正确的做法。然而,在一个支持其他实现方式的平台上,我并没有看到任何实际的理由来支持这种无用且有问题的实现方式。如果在某些特定的平台和类型组合下无法实现,那么即使使用 C 风格的强制类型转换等方法也无济于事。 - Sergei Tachenov
2
@bstamour "reinterpret_cast是一种按位转换" 不行。 - curiousguy
1
@bstamour [expr.reinterpret.cast] "[注意:reinterpret_cast执行的映射可能会产生与原始值不同的表示。—注释结束]" - curiousguy
显示剩余5条评论
5个回答

12
没有保证将char*强制转换为unsigned char*不会在您尝试解引用unsigned char*指针时导致程序崩溃。
由于没有其他方法可以进行此类转换,因此您必须信任编译器对这种完全合理的转换所做的处理。
既然reinterpret_cast似乎不能保证这确实有效,那么C样式的强制转换是否是唯一安全的选项?
C样式的强制转换只是映射到reinterpret_cast,因此它们完全相同。在某些点上,您必须信任您的编译器。标准有一个限制,它仅仅说“不行,请阅读您的编译器手册”。当涉及跨类型强制转换时,这就是这样的一个点。它允许您使用unsigned char lvalue读取char。不能将char *转换为可用的unsigned char *进行此类操作的编译器实际上是无法使用的,并且由于这个原因不存在。

2
“所以你必须相信编译器对这个完全合理的转换所做的事情。” 寻找 traits::length(reinterpret_cast<const char*>(s)):标准库也假定了同样的“信任”。哈哈 - curiousguy
标准有一个限制,它只是简单地说“不行,请阅读您的编译器手册”。对此要加1分。有时候,我们必须接受标准并没有明确允许我们想要的内容,但是我们的编译器可以,这就足够了。GCC和Clang将reinterpret_cast视为大多数人认为的“按位转换”,而且cppreference.com(也许不太妥当)也是这样描述的。虽然我希望GCC能够记录这一点,但我似乎找不到。 - underscore_d
@underscore_d:只有编译器的作者专注于做合理和有用的事情,而不是仅仅满足标准所要求的最低限度,这种哲学才能够良好地发挥作用。如果标准的作者们希望编译器的作者采取后一种方式,我认为标准会被写得非常不同。 - supercat

4
基本上,指向指针 reinterpret_cast 操作的结果除了被重新转换回原始指针类型以外,不能用于其他任何安全操作。
你是对的,标准确实出现了问题,但N3242试图修复这个问题: reinterpret_cast<T*> 的值的定义
N3242,[expr.reinterpret.cast]:
当类型为“指向 T1”的 prvalue v 转换为类型“指向 cv T2”的 prvalue 时,如果 T1 和 T2 都是标准布局类型(3.9),并且 T2 的对齐要求不比 T1 更严格,则结果为 static_cast <cv T2*>(static_cast <cv void*>(v))
这仍未定义任何内容;有趣的是关于 static_cast 的无关文本:
当类型为“指向 cv1 void”的 prvalue 可以转换为类型为“指向 cv2 T”的 prvalue,其中 T 是对象类型,而 cv2 是与 cv1 相同的 cv 限定符或更大的 cv 限定符。空指针值转换为目标类型的空指针值。将类型为对象指针的值转换为“指向 cv void”的值然后再转换回去(可能具有不同的 cv-qualification),应保持其原始值。
"N3242, [class.mem] 中"适当转换""
通过使用 reinterpret_cast 适当转换的指针指向标准布局结构对象,则指向其初始成员(或如果该成员是位域,则指向它所在的单元),反之亦然。
这是什么样的风格? "suitably"?哈哈
显然,这些人不知道如何编写规范。
在这里,“C式转换”是唯一安全的选择吗?
这并没有帮助。
标准已经出现了问题,重大的问题。
但是,每个人都理解它的真实含义。标准的潜台词是:
当类型为“指向 T1”的 prvalue v 转换为类型“指向 cv T2”的时候,结果 is static_cast<cv T2*>(static_cast<cv void*>(v))指向内存地址,如果 T1 和 T2 都是标准布局类型(3.9),并且 T2 的对齐要求不比 T1 更严格。
这显然并不完美(但并不比标准中的许多其他部分更糟),但是两分钟内,我就能写出一个比委员会十年都写得好的规范。

不错的尝试!他们在这方面有进展吗?令人遗憾的是,根据99%的人的直觉默默实现的编译器写作者的某些内容仍然没有正式定义。 - underscore_d

3
标准中还有一些其他的保证(请参见类型表示部分,该部分规定相应的无符号和有符号类型共享常见值的表示。还有一些文本保证您可以将任何东西读取为字符)。但是请注意,即使在您正在阅读的此部分,也有一些地方会减轻规定(这表明事情是实现定义和未指定的):某些形式的类型转换是未定义行为。

2

该标准规定了在所有平台上必须发生的事情,您不必这样做。如果您将可移植性要求限制为实际支持reinterpret_cast的平台,那么可以。

在实际支持uint16_t的平台上,转换可能有效。在char16_t宽度为18、24、32或36位的平台上,可能无法正确执行。问题是,您是否必须支持这些平台?语言标准希望如此。


0
指针转换操作的结果不能安全地用于除了强制转换回原始指针类型之外的任何其他用途。
这听起来不太对。假设sizeof(void *) > sizeof(int *)void *foo; reinterpret_cast<void *>(reinterpret_cast<int *>(foo))可能会使您得到一个截断的指针值。
难道不是reinterpret_cast在实践中只是等同于C的默认转换吗?

1
标准中有一个额外的条件:中间类型不能有更强的对齐约束。 - AProgrammer
3
reinterpret_cast绝对不等同于C风格的强制类型转换。它不能修改constvolatile,也不能在各种函数指针类型之间进行转换,等等。 - Mark B

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