【发布时间】:2011-10-20 05:55:05
【问题描述】:
我听说reinterpret_cast 是实现定义的,但我不知道这究竟意味着什么。你能举个例子说明它是如何出错的,它出错了,使用 C-Style cast 更好吗?
【问题讨论】:
我听说reinterpret_cast 是实现定义的,但我不知道这究竟意味着什么。你能举个例子说明它是如何出错的,它出错了,使用 C-Style cast 更好吗?
【问题讨论】:
C 风格的演员表并不好。
它只是按顺序尝试各种 C++ 风格的强制转换,直到找到一个有效的强制转换。这意味着当它像reinterpret_cast 一样工作时,它的问题与reinterpret_cast 完全相同。但除此之外,它还有这些问题:
reinterpret_cast、const_cast 或 static_cast,而那些非常不同的东西)reinterpret_cast 很容易找到,这很好,因为演员表很难看,使用时要注意。相反,通过搜索可靠地找到 C 风格的演员表(如 (int)42.0)要困难得多要回答您问题的另一部分,是的,reinterpret_cast 是实现定义的。这意味着当您使用它从int* 转换为float* 时,您无法保证生成的指针将指向相同的地址。那部分是实现定义的。但是,如果您将生成的float* 和reinterpret_cast 取回int*,那么您将获得原始指针。这部分是有保证的。
但是,请记住,无论您使用 reinterpret_cast 还是 C 样式转换,这都是正确的:
int i;
int* p0 = &i;
float* p1 = (float*)p0; // implementation-defined result
float* p2 = reinterpret_cast<float*>(p0); // implementation-defined result
int* p3 = (int*)p1; // guaranteed that p3 == p0
int* p4 = (int*)p2; // guaranteed that p4 == p0
int* p5 = reinterpret_cast<int*>(p1); // guaranteed that p5 == p0
int* p6 = reinterpret_cast<int*>(p2); // guaranteed that p6 == p0
【讨论】:
reinterpret_cast 从 A 到 B 会产生一个实现定义的结果,但 A 到 B 到 A 保证提供与您开始时相同的值。
A 的大小需要适合B 的大小,以保证A 到B 到A 的转换序列正常工作.将指针投射到非指针字符并返回显然会导致流泪。不太明显的是,在一些晦涩的硬件平台上指针的存储大小可能会根据指向的类型而有所不同,唯一的保证是 void* 需要能够适应其中的任何一个。
它是在某种意义上定义的实现,标准没有(几乎)规定不同类型的值在位级别上的外观,地址空间的结构等等。所以它确实是一个非常特定于转换的平台,例如:
double d;
int &i = reinterpret_cast<int&>(d);
但正如标准所说
它旨在让那些知道寻址结构的人不足为奇 底层机器。
因此,如果您知道自己在做什么以及在低级别上的外观如何,就不会出错。
C 风格的强制转换在某种意义上有点相似,它可以执行 reinterpret_cast,但它也首先“尝试”static_cast,它可以抛弃 cv 限定(而 static_cast 和 reinterpret_cast 不能)并执行不考虑访问控制的转换(参见 C++11 标准中的 5.4/4)。例如:
#include <iostream>
using namespace std;
class A { int x; };
class B { int y; };
class C : A, B { int z; };
int main()
{
C c;
// just type pun the pointer to c, pointer value will remain the same
// only it's type is different.
B *b1 = reinterpret_cast<B *>(&c);
// perform the conversion with a semantic of static_cast<B*>(&c), disregarding
// that B is an unaccessible base of C, resulting pointer will point
// to the B sub-object in c.
B *b2 = (B*)(&c);
cout << "reinterpret_cast:\t" << b1 << "\n";
cout << "C-style cast:\t\t" << b2 << "\n";
cout << "no cast:\t\t" << &c << "\n";
}
这是 ideone 的输出:
reinterpret_cast: 0xbfd84e78 C 风格转换:0xbfd84e7c 没有演员表:0xbfd84e78请注意,reinterpret_cast 产生的值与 'c' 的地址完全相同,而 C 风格的转换产生了正确的偏移指针。
【讨论】:
void* (关于对象类型) 使用static_cast 的往返行程将起作用。在这种情况下,结果是相同的,但可能会阻止您意外转换为错误的类型。
使用reinterpret_cast 是有正当理由的,由于这些原因,标准实际上定义了会发生什么。
第一种是使用不透明指针类型,用于库 API 或只是将各种指针存储在单个数组中(显然连同它们的类型一起)。您可以将指针转换为适当大小的整数,然后再转换回指针,它将是完全相同的指针。例如:
T b;
intptr_t a = reinterpret_cast<intptr_t>( &b );
T * c = reinterpret_cast<T*>(a);
在此代码中,c 保证如您预期的那样指向对象 b。转换回不同的指针类型当然是未定义的(有点)。
函数指针和成员函数指针允许进行类似的转换,但在后一种情况下,您可以转换为/从另一个成员函数指针转换为具有足够大的变量。
第二种情况是使用标准布局类型。这是 C++11 之前事实上支持的东西,现在已在标准中指定。在这种情况下,标准首先将 reinterpret_cast 视为 static_cast 到 void*,然后是 static_cast 到目标类型。这在执行二进制协议时经常使用,其中数据结构通常具有相同的标头信息,并允许您转换具有相同布局但 C++ 类结构不同的类型。
在这两种情况下,您都应该使用显式的reinterpret_cast 运算符而不是C 样式。虽然 C 风格通常会做同样的事情,但它有遭受重载转换运算符的危险。
【讨论】:
C++ 有类型,它们通常相互转换的唯一方法是通过您编写的明确定义的转换运算符。一般来说,这就是您编写程序所需要和应该使用的全部内容。
但是,有时您希望将表示类型的位重新解释为其他内容。这通常用于非常低级的操作,而不是您通常应该使用的东西。对于这些情况,您可以使用reinterpret_cast。
它是实现定义的,因为 C++ 标准并没有真正说明应该如何在内存中实际布局。这由您对 C++ 的特定实现控制。因此,reinterpret_cast 的行为取决于您的编译器如何在内存中布局结构以及它如何实现reinterpret_cast。
C 风格的强制转换与reinterpret_casts 非常相似,但它们的语法要少得多,因此不推荐使用。人们认为强制转换本质上是一个丑陋的操作,它需要丑陋的语法来通知程序员正在发生一些可疑的事情。
一个可能出错的简单示例:
std::string a;
double* b;
b = reinterpret_cast<double*>(&a);
*b = 3.4;
那个程序的行为是未定义的——编译器可以做任何它喜欢的事情。很可能,当string 的析构函数被调用时,你会崩溃,但谁知道呢!它可能只会损坏您的堆栈并导致不相关的函数崩溃。
【讨论】:
reinterpret_cast 和 c 风格的转换都是实现定义的,它们做的事情几乎相同。区别在于:
1.reinterpret_cast 无法移除常量。例如:
const unsigned int d = 5;
int *g=reinterpret_cast< int* >( &d );
会报错:
error: reinterpret_cast from type 'const unsigned int*' to type 'int*' casts away qualifiers
2。如果你使用reinterpret_cast,很容易找到你做的地方。不可能使用 c 风格的演员表
【讨论】:
C 风格的强制转换有时会以未指定的方式对对象进行类型双关语,例如 (unsigned int)-1,有时会将相同的值转换为不同的格式,例如 (double)42,有时也可以这样做,例如 (void*)0xDEADBEEF重新解释位,但(void*)0 保证为空指针常量,它不一定具有与(intptr_t)0 相同的对象表示,并且很少告诉编译器执行shoot_self_in_foot_with((char*)&const_object); 之类的操作。
这通常都很好,但是当您想将double 转换为uint64_t 时,有时您需要值,有时您需要位。如果您了解 C,您就知道 C 风格的转换是哪一种,但在某些方面为两者使用不同的语法会更好。
Bjarne Stroustrup 在他的指导方针中,在另一种情况下推荐了 reinterpret_cast:如果你想以一种语言没有由 static_cast 定义的方式来键入双关语,他建议你使用类似 @ 987654331@ 而不是其他方法。它们都是未定义的行为,但这使得你在做什么以及你是故意这样做的非常明确。阅读与您上次写信不同的工会成员不会。
【讨论】:
double 的位视为 uint64_t,以便您可以旋转它们。尤其是当方法声明union、写入一种类型的成员并读出不同类型的成员时(这在 C 中是合法的,但如您所知,在 C++ 中是未定义的行为)。
void* 到uintptr_t 的转换也是如此。另一方面,从double 到uint64_t 的reinterpret_cast 是类型双关语,而C 风格的转换具有static_cast 的语义,尽可能接近地表示值。
reinterpret_cast 确实进行了重新解释。您之前的评论并不清楚使用了引用类型。