60

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?

fduff
  • 3,671
  • 2
  • 30
  • 39
user103214
  • 3,478
  • 6
  • 26
  • 37

6 Answers6

56

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, a const_cast or a static_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
DaveRandom
  • 87,921
  • 11
  • 154
  • 174
jalf
  • 243,077
  • 51
  • 345
  • 550
  • 1
    Would the implementation-defined result differ if i run it on a different compiler or on the same one again? Does it mean the code would be non-portable? – user103214 Oct 20 '11 at 10:16
  • Implementation-defined means that the implementation (the compiler, basically) can choose how to behave, but it must document the behavior. So typically, that means that a single compiler will do the same thing consistently if you recompile, or if you run the program again. But if you compile using a different compiler, or targeting a difference CPU, it might do something else (which, again, has to be documented). So yes, if you rely on the intermediate value, your code is non-portable. If you want to be portable, you have to do the full round-trip – jalf Oct 20 '11 at 11:36
  • I see some conversions you showed are guaranteed, does it depend on the type of objects we are interpreting e.g float* to int* or is it because reinterpret_cast works that way. how about some abstract types? – user103214 Oct 22 '11 at 07:29
  • 1
    @user974191 That's what I tried to say above the example. `reinterpret_cast` from `A` to `B` yields an implementation-defined result, but `A` to `B` to `A` is guaranteed to give the same value as you started with. – jalf Oct 22 '11 at 07:50
  • What I tried to show is that because a C-style cast when applied to pointers behaves exactly like a reinterpret-cast, you can mix them and get the same result (C-style cast from A to B, and the reinterpret cast from B to A, is the same as reinterpret-casting both ways) – jalf Oct 22 '11 at 07:51
  • You CAN you define uses less typing as better. C++ is a cumbersome language, and if you're doing low level stuff you're probably used to C. If it works in C then it works in C++. If the typing slows you down to the point where it jeopardizes your timeline, then the crude method MAY be preferred. –  May 27 '18 at 17:16
  • 3
    @jalf Great answer, and best I found in an hour's googling. Have a nitpick o share: Stroustrup mentions in "The C++ Programming Language" that the size of `A` needs to fit into the size of `B` for the guaranteed `A` to `B` to `A` conversion sequence to work. Casting a pointer to a non-pointer char and back will obviously lead to tears. Less obvious is that the storage size of pointers on some obscure hardware platforms can vary depending on the type pointed to, with the only guarantee being a void* needs to be able to fit any of them. – Rob Nov 05 '18 at 20:48
  • Excerpt from The C++ Programming Language can (hopefully) be found [here](https://books.google.co.za/books?id=PSUNAAAAQBAJ&pg=PA302&lpg=PA302&dq=stroustrup+reinterpret_cast&source=bl&ots=DrvlHhf2bK&sig=vVi2rSYcP05SEy7RB0gekIxVTHA&hl=en&sa=X&ved=2ahUKEwiyn5Xrh77eAhUBC8AKHQz2B_IQ6AEwFXoECAgQAQ#v=onepage&q=stroustrup%20reinterpret_cast&f=false). – Rob Nov 05 '18 at 20:49
  • Now I wonder whether using c-style cast to cast from `int*` to `float*` guarantees the resulting pointer points to the same address? Assuming both `int` and `float` are 4-byte. – HCSF Nov 04 '21 at 06:17
20

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.

Donald Duck
  • 8,409
  • 22
  • 75
  • 99
Konstantin Oznobihin
  • 5,234
  • 24
  • 31
  • 1
    The standard provides several guarantees and requirements for reinterpret_cast. The most important is that round-trip conversion, pointer to integer to pointer yields the original pointer. Furthermore, C++ solidifies the expected behaviour of casting with standard layout types. – edA-qa mort-ora-y Oct 20 '11 at 07:43
  • 2
    @edA-qa mort-ora-y: Assuming the integer is large enough to hold the pointer. The most important one (IMO) is round trip from A* -> void* -> A* yields the original. – Martin York Oct 20 '11 at 08:41
  • @LokiAstari: when A is an object type :) Anyhow, if one needs a full and precise picture it's better to take a look at the standard. – Konstantin Oznobihin Oct 20 '11 at 09:04
  • 1
    For `void*` _(on object types)_ round-trip using `static_cast` will work. The result is the same in this case, but might prevent you from accidentally casting to the wrong type. – edA-qa mort-ora-y Oct 20 '11 at 09:05
  • @edA-qamort-ora-y: Yes it is 5.2.10/4 "A pointer can be explicitly converted to any integral type **large enough to hold it**" 5.2.10/7 "pointer to T1” is converted to the type “pointer to cv T2”, the result is static_cast(static_cast(v))" **Thus reinterpret cast works** for casting too and from void*. And more importantly in my eyes. reinterpret_cast<> sticks out in the code more than static_cast<> and thus has a higher degree of scrutiny. – Martin York Oct 20 '11 at 09:22
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_casts, 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.

Owen
  • 7,494
  • 10
  • 42
  • 52
Ayjay
  • 3,413
  • 15
  • 20
  • 1
    Is the last bit implementation or undefined behavior (there is an important distinction). – Martin York Oct 20 '11 at 07:27
  • What's the difference between undefined and implementation defined? – Ayjay Oct 20 '11 at 08:31
  • 3
    1.3.10 **implementation-defined behavior** behavior, for a well-formed program construct and correct data, that depends on the implementation and that each implementation documents. 1.3.24: **undefined behavior** behavior for which this International Standard imposes no requirements. 1.3.25 **unspecified behavior** behavior, for a well-formed program construct and correct data, that depends on the implementation. Get a copy of the standard here: http://stackoverflow.com/questions/81656/where-do-i-find-the-current-c-or-c-standard-documents/4653479#4653479 – Martin York Oct 20 '11 at 08:38
  • UB is the nasal deamons. ID is for this particular compiler/target couple, with specs in the manual rather than the standard. – v.oddou Jul 07 '20 at 02:26
4

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.

edA-qa mort-ora-y
  • 30,295
  • 39
  • 137
  • 267
2

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

BЈовић
  • 62,405
  • 41
  • 173
  • 273
0

C-style casts sometimes type-pun an object in an unspecified way, such as (unsigned int)-1, sometimes convert the same value to a different format, such as (double)42, sometimes could do either, like how (void*)0xDEADBEEF reinterprets bits but (void*)0 is guaranteed to be a null pointer constant, which does not necessarily have the same object representation as (intptr_t)0, and very rarely tells the compiler to do something like shoot_self_in_foot_with((char*)&const_object);.

That's usually all well and good, but when you want to cast a double to a uint64_t, sometimes you want the value and sometimes you want the bits. If you know C, you know which one the C-style cast does, but it's nicer in some ways to have different syntax for both.

Bjarne Stroustrup, in his guidelines, recommended reinterpret_cast in another context: if you want to type-pun in a way that the language does not define by a static_cast, he suggested that you do it with something like reinterpret_cast<double&>(uint64) rather than the other methods. They're all undefined behavior, but that makes it very explicit what you're doing and that you're doing it on purpose. Reading a different member of a union than you last wrote to does not.

Davislor
  • 14,674
  • 2
  • 34
  • 49
  • What is a "type-pun"? – curiousguy Jul 16 '18 at 15:07
  • @curiousguy That’s when you reinterpret the bits of some object’s binary representation as if it were a different type. For example, treating the bits of a 64-bit `double` as a `uint64_t` so you can twiddle them. Especially when the method is declaring a `union`, writing to a member of one type, and reading out a member of a different type (which is legal in C, but, as you know, undefined behavior in C++). – Davislor Jul 16 '18 at 16:23
  • @curiousguy Certain integer casts do. A cast between signed and unsigned integral types does, and so does a cast from `void*` to `uintptr_t`. On the other hand, a `reinterpret_cast` from `double` to `uint64_t` is a type-pun, while a C-style cast has the semantics of `static_cast`, which represents the value as closely as possible. – Davislor Jul 16 '18 at 17:32
  • @curiousguy [expr.reinterpret.cast.11], although that says it needs to be cast to a reference. – Davislor Jul 16 '18 at 18:40
  • Yes indeed a `reinterpret_cast` to a reference type does a reinterpretation. That a reference type was used wasn't clear from your previous comment. – curiousguy Jul 17 '18 at 13:10