Reinterpret_cast与C风格演员

时间:2011-10-20 05:55:06

标签: c++ casting

我听说reinterpret_cast是实现定义的,但我不知道这究竟意味着什么。你能提供一个如何出错的例子,它出错了,使用C-Style演员会更好吗?

6 个答案:

答案 0 :(得分:31)

C风格演员阵容并不是更好。

它只是按顺序尝试各种C ++风格的强制转换,直到它找到一个有效的。这意味着,当它像reinterpret_cast一样时,它与reinterpret_cast具有完全相同的问题。但此外,它还存在以下问题:

  • 它可以做很多不同的事情,并且通过阅读代码调用哪种类型的强制转换并不总是很清楚(它可能表现得像reinterpret_castconst_cast或{{1}那些做不同的事情)
  • 因此,更改周围的代码可能会改变演员的行为
  • 在阅读或搜索代码时很难找到 - static_cast很容易找到,这很好,因为演员表很难看,使用时应该注意。相反,通过搜索可以更难找到C风格的演员(如reinterpret_cast

要回答问题的其他部分,是的,(int)42.0是实现定义的。这意味着当您使用它从reinterpret_cast转换为int*时,您无法保证结果指针将指向同一地址。那部分是实现定义的。但是,如果您将结果float*float*带回reinterpret_cast,那么您将获得原始指针。那部分是有保障的。

但同样,请记住,无论您使用int*还是使用C风格演员,都是如此:

reinterpret_cast

答案 1 :(得分:15)

在某种意义上定义的实现是标准不(几乎)规定不同类型值在位级上应该是什么样的,应该如何构造地址空间等等。所以它真的是一个非常适合转换的平台,例如:

double d;
int &i = reinterpret_cast<int&>(d);

然而,正如标准所说

  

对于那些了解寻址结构的人来说,这并不奇怪   底层机器。

所以,如果你知道自己做了什么以及在低级别看起来如何,那么就不会出错。

C风格的转换在某种意义上可以执行reinterpret_cast,但它也首先“尝试”static_cast并且它可以抛弃cv限定(而static_cast和reinterpret_cast不能)并执行转换而忽略访问控制(参见C ++ 11标准中的5.4 / 4)。 E.g:

#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-style cast:      0xbfd84e7c
no cast:           0xbfd84e78

请注意,reinterpret_cast产生的值与'c'的地址完全相同,而C样式的强制转换导致正确的偏移指针。

答案 2 :(得分:5)

有正当理由使用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 *视为void *,然后将static_cast视为desination类型。在进行二进制协议时会使用很多,其中数据结构通常具有相同的头信息,并允许您转换具有相同布局但C ++类结构不同的类型。

在这两种情况下,您应该使用显式reinterpret_cast运算符而不是C-Style。虽然C风格通常会做同样的事情,但它有遭受过载转换运算符的危险。

答案 3 :(得分:3)

C ++有类型,它们通常在彼此之间进行转换的唯一方法是使用您编写的定义良好的转换运算符。一般来说,这就是你们所有人都应该用来编写程序的。

但是,有时您希望将表示类型的位重新解释为其他内容。这通常用于非常低级别的操作,而不是您通常应该使用的操作。对于这些情况,您可以使用reinterpret_cast

它是实现定义的,因为C ++标准并没有真正说明事情应该如何在内存中实际布局。这是由您特定的C ++实现控制的。因此,reinterpret_cast的行为取决于编译器如何在内存中放置结构以及它如何实现reinterpret_cast

C风格的强制转换与reinterpret_cast非常相似,但它们的语法要少得多,不推荐使用。我们的想法是,转换本质上是一个丑陋的操作,它需要丑陋的语法来通知程序员发生了一些可疑的事情。

一个简单的例子,说明它可能出错:

std::string a;
double* b;
b = reinterpret_cast<double*>(&a);
*b = 3.4;

该程序的行为未定义 - 编译器可以做任何它喜欢的事情。最有可能的是,当string的析构函数被调用时,你会崩溃,但谁知道呢!它可能只会损坏您的堆栈并导致无关函数崩溃。

答案 4 :(得分:1)

reinterpret_cast和c-style强制转换都是实现定义的,它们几乎完全相同。不同之处是:
1. reinterpret_cast无法移除constness。例如:

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风格的演员阵容

是不可能的

答案 5 :(得分:0)

C风格的转换有时会以未指定的方式对对象进行类型打孔,例如__call__,有时会将相同的值转换为不同的格式,例如class DynamicLoadState: def __call__(self, driver): LoadComplete = False if driver.execute_script("return document.readyState") == 'complete': LoadComplete = True return LoadComplete WebDriverWait(self.driver, 10).until(DynamicLoadState()) ,有时可能会执行两种操作,例如(unsigned int)-1如何重新解释位,但是保证(double)42是一个空指针常量,它不一定具有与(void*)0xDEADBEEF相同的对象表示,并且很少告诉编译器执行类似{ {1}}。

通常都很好,但是当您要将(void*)0强制转换为(intptr_t)0时,有时您需要该值,而有时又需要这些位。如果您了解C,就知道C样式强制转换是哪一种,但是从某种角度来说,为两者使用不同的语法会更好。

Bjarne Stroustrup在他的指南中建议在另一种情况下使用shoot_self_in_foot_with((char*)&const_object);:如果您想以一种double所不定义的方式来输入双关语,他建议您这样做使用uint64_t之类的东西,而不是其他方法。它们都是不确定的行为,但这使您正在做的事情非常明确,并且您是故意这样做的。读与您上次写的不同的工会成员不会。