Reinterpret_cast vs. C-style cast
I hear that reinterpret_cast
is implementation defined, but I don't know what this really means. Can you provide an example of how it can go wrong, and it goes wrong, is it better to use C-Style cast?
Solution 1:
The C-style cast isn't better.
It simply tries the various C++-style casts in order, until it finds one that works. That means that when it acts like a reinterpret_cast
, it has the exact same problems as a reinterpret_cast
. But in addition, it has these problems:
- It can do many different things, and it's not always clear from reading the code which type of cast will be invoked (it might behave like a
reinterpret_cast
, aconst_cast
or astatic_cast
, and those do very different things) - Consequently, changing the surrounding code might change the behaviour of the cast
- It's hard to find when reading or searching the code -
reinterpret_cast
is easy to find, which is good, because casts are ugly and should be paid attention to when used. Conversely, a C-style cast (as in(int)42.0
) is much harder to find reliably by searching
To answer the other part of your question, yes, reinterpret_cast
is implementation-defined. This means that when you use it to convert from, say, an int*
to a float*
, then you have no guarantee that the resulting pointer will point to the same address. That part is implementation-defined. But if you take the resulting float*
and reinterpret_cast
it back into an int*
, then you will get the original pointer. That part is guaranteed.
But again, remember that this is true whether you use reinterpret_cast
or a C-style cast:
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
Solution 2:
It is implementation defined in a sense that standard doesn't (almost) prescribe how different types values should look like on a bit level, how address space should be structured and so on. So it's really a very platform specific for conversions like:
double d;
int &i = reinterpret_cast<int&>(d);
However as standard says
It is intended to be unsurprising to those who know the addressing structure of the underlying machine.
So if you know what you do and how it all looks like on a low-level nothing can go wrong.
The C-style cast is somewhat similar in a sense that it can perform reinterpret_cast, but it also "tries" static_cast first and it can cast away cv qualification (while static_cast and reinterpret_cast can't) and perform conversions disregarding access control (see 5.4/4 in C++11 standard). 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";
}
and here is an output from ideone:
reinterpret_cast: 0xbfd84e78 C-style cast: 0xbfd84e7c no cast: 0xbfd84e78
note that value produced by reinterpret_cast is exactly the same as an address of 'c', while C-style cast resulted in a correctly offset pointer.
Solution 3:
There are valid reasons to use reinterpret_cast
, and for these reasons the standard actually defines what happens.
The first is to use opaque pointer types, either for a library API or just to store a variety of pointers in a single array (obviously along with their type). You are allowed to convert a pointer to a suitably sized integer and then back to a pointer and it will be the exact same pointer. For example:
T b;
intptr_t a = reinterpret_cast<intptr_t>( &b );
T * c = reinterpret_cast<T*>(a);
In this code c
is guaranteed to point to the object b
as you'd expected. Conversion back to a different pointer type is of course undefined (sort of).
Similar conversions are allowed for function pointers and member function pointers, but in the latter case you can cast to/from another member function pointer simply to have a variable that is big enouhg.
The second case is for using standard layout types. This is something that was de factor supported prior to C++11 and has now been specified in the standard. In this case the standard treats reinterpret_cast as a static_cast to void* first and then a static_cast to the desination type. This is used a lot when doing binary protocols where data structures often have the same header information and allows you to convert types which have the same layout, but differ in C++ class structure.
In both of these cases you should use the explicit reinterpret_cast
operator rather than the C-Style. Though the C-style would normally do the same thing, it has the danger of being subjected to overloaded conversion operators.
Solution 4:
C++ has types, and the only way they normally convert between each other is by well-defined conversion operators that you write. In general, that's all you both need and should use to write your programs.
Sometimes, however, you want to reinterpret the bits that represent a type into something else. This is usually used for very low-level operations and is not something you should typically use. For those cases, you can use reinterpret_cast
.
It is implementation defined because the C++ standard does not really say much at all about how things should actually be laid out in memory. That is controlled by your specific implementation of C++. Because of this, the behaviour of reinterpret_cast
depends upon how your compiler lays structures out in memory and how it implements reinterpret_cast
.
C-style casts are quite similar to reinterpret_cast
s, but they have much less syntax and are not recommended. The thinking goes that casting is inherently an ugly operation and it requires ugly syntax to inform the programmer that something dubious is happening.
An easy example of how it could go wrong:
std::string a;
double* b;
b = reinterpret_cast<double*>(&a);
*b = 3.4;
That program's behaviour is undefined - a compiler could do anything it likes to that. Most probably, you would get a crash when the string
's destructor is called, but who knows! It might just corrupt your stack and cause a crash in an unrelated function.
Solution 5:
Both reinterpret_cast
and c-style casts are implementation defined and they do almost the same thing. The differences are :
1. reinterpret_cast
can not remove constness. For example :
const unsigned int d = 5;
int *g=reinterpret_cast< int* >( &d );
will issue an error :
error: reinterpret_cast from type 'const unsigned int*' to type 'int*' casts away qualifiers
2. If you use reinterpret_cast
, it is easy to find the places where you did it. It is not possible to do with c-style casts