21

I've read various previous questions about the use of reinterpret_cast, and I've also read the relevant wording in the C++ standard. Essentially, what it comes down to is that the result of a pointer-to-pointer reinterpret_cast operation can't safely be used for anything other than being cast back to the original pointer type.

In practice, however, most real-world uses of reinterpret_cast seem to be based on the (wrong) assumption that a reinterpret_cast is the same as a C-style cast. For example, I've seen lots of code which uses reinterpret_cast to cast from char* to unsigned char* for the purpose of character set conversion routines. This is completely harmless, yet strictly speaking it's not portable - there's no guarantee that a reinterpret_cast from char* to unsigned char* won't crash your program when you try to dereference the unsigned char* pointer.

It's seems the only other real use of reinterpret_cast that has any real guarantees, according to the standard, is converting from pointer to integer, and vice-versa.

And yet there are many cases where we'd want (and should be able to) safely convert between different pointer types. For example: uint16_t* to the new C++0x char16_t*, or really any pointer to a basic data type that is the same size/alignment as the original type. Yet reinterpret_cast provides no guarantees this should work.

Question: How can we safely convert between pointers to basic data-types of the same size/alignment, such as char* --> unsigned char*? Since reinterpret_cast doesn't seem to guarantee this actually works, are C-style casts the only safe option here?

Community
  • 1
  • 1
Channel72
  • 24,139
  • 32
  • 108
  • 180
  • @Channel72: Yesterday I learned that it's not useless. See this : [Why do we have reinterpret_cast in C++ when two chained static_cast can do it's job?](http://stackoverflow.com/questions/5025843/why-do-we-have-reinterpret-cast-in-c-when-two-chained-static-cast-can-do-its-j) – Nawaz Feb 20 '11 at 14:35
  • 7
    C-style casts aren't guaranteed to work either. – sbi Feb 20 '11 at 14:40
  • C-style casts will never work like the way dynamic_casts do! – Nawaz Feb 20 '11 at 14:41
  • 3
    C-style casts are, at least in C++, exactly defined in terms of static_cast, const_cast, and reinterpret_cast. (Except for the lone oddity that C-style casts can ignore accessibility.) – Thomas Edleson Feb 20 '11 at 15:28
  • I'd like to see a platform where `char*` and `unsigned char*` are compatible types and yet `reinterpret_cast<>` doesn't work for them. If it doesn't work, it either means that the compiler developers are evil or crazy, or that there are fundamental differences between pointers to signed and unsigned types on that platform. If it's the former, which is unlikely, you're out of luck. If it's the latter, you probably don't want to do that cast anyway. – Sergei Tachenov Feb 20 '11 at 15:36
  • reinterpret_cast is a bit-for-bit cast, so there should be no cases in which char* and unsigned char* are compatible yet reinterpret_cast doesn't work. – bstamour Feb 20 '11 at 16:12
  • 4
    @bstamour, strictly speaking it's not "bit-for-bit", it's "implementation defined", which could theoretically mean anything. And there is no guarantee that "bit-for-bit" is always the correct way to cast pointers either. However, I just don't see any practical reason for it to be implemented in a useless and broken way on a platform where it is possible to implement it otherwise. And if it's impossible on a particular platform for a particular pair of types, well, nothing will help you then, including C-style casts or whatever. – Sergei Tachenov Feb 20 '11 at 16:42
  • 2
    @bstamour "_reinterpret_cast is a bit-for-bit cast_" No. – curiousguy Dec 11 '11 at 17:24
  • 1
    @bstamour [expr.reinterpret.cast] "[ Note: The mapping performed by reinterpret_cast might, or might not, produce a representation different from the original value. —end note ]" – curiousguy Dec 13 '11 at 05:17
  • 1
    @ThomasEdleson: How do you mean `C-style casts can ignore accessibility`? Can you provide an example, because I've never heard of this before. – Thomas Eding Feb 11 '14 at 00:22

5 Answers5

12

there's no guarantee that a reinterpret_cast from char* to unsigned char* won't crash your program when you try to dereference the unsigned char* pointer.

You can't do such a cast in any other way, so you have to have to trust what your compiler does with this completely reasonable cast.

Since reinterpret_cast doesn't seem to guarantee this actually works, are C-style casts the only safe option here?

The C-style cast will just map to reinterpret_cast so it will be exactly the same. At some point you have to trust your compiler. The Standard has a limit on which it simply says "no. read your compiler's manual". When it comes to cross-casting pointers, this is such a point. It allows you to read a char using an unsigned char lvalue. A compiler that cannot cast a char* to a usable unsigned char* to do such is just about unusable and doesn't exist for that reason.

Johannes Schaub - litb
  • 496,577
  • 130
  • 894
  • 1,212
  • 2
    "_so you have to have to trust what your compiler does with this completely reasonable cast_" Look for `traits::length(reinterpret_cast(s))`: the standard library assumes the same "trust". lol – curiousguy Dec 13 '11 at 05:44
  • ' The Standard has a limit on which it simply says "no. read your compiler's manual". ' +1 for this. Sometimes, we have to accept that the Standard doesn't specifically allow what we want, but our compiler will, and that has to be good enough. GCC and Clang treat `reinterpret_cast` as the 'bitwise cast' most people assume it is, and indeed `cppreference.com` (perhaps inadvisably) describes it this way too. I wish GCC documented this, though.... I can't seem to find that. – underscore_d Jan 07 '16 at 12:51
  • @underscore_d: That philosophy can only work well if compiler writers focus on doing what is reasonable and useful rather than on doing the minimum required by the Standard. If the authors of the Standard had expected compiler writers to adopt the latter focus, I think it would have been written very differently. – supercat Apr 23 '17 at 18:21
4

Essentially, what it comes down to is that the result of a pointer-to-pointer reinterpret_cast operation can't safely be used for anything other than being cast back to the original pointer type.

You are right, the standard was broken, but N3242 tries to fix that:

Definition of the value of reinterpret_cast<T*>

N3242, [expr.reinterpret.cast]:

When a prvalue v of type “pointer to T1” is converted to the type “pointer to cv T2”, the result is static_cast<cv T2*>(static_cast<cv void*>(v)) if both T1 and T2 are standard-layout types (3.9) and the alignment requirements of T2 are no stricter than those of T1.

This still defines nothing; for fun, the less irrelevant text about static_cast:

A prvalue of type “pointer to cv1 void” can be converted to a prvalue of type “pointer to cv2 T,” where T is an object type and cv2 is the same cv-qualification as, or greater cv-qualification than, cv1. The null pointer value is converted to the null pointer value of the destination type. A value of type pointer to object converted to “pointer to cv void” and back, possibly with different cv-qualification, shall have its original value.

"Suitably converted"

N3242, [class.mem]:

A pointer to a standard-layout struct object, suitably converted using a reinterpret_cast, points to its initial member (or if that member is a bit-field, then to the unit in which it resides) and vice versa.

What kind of style is that? "suitably"? lol

Clearly, these guys don't know how to write a specification.

C-style casts the only safe option here?

It doesn't help.

The standard is broken, broken, broken.

But everybody understands what it really means. The subtext of the standard says:

When a prvalue v of type “pointer to T1” is converted to the type “pointer to cv T2”, the result is static_cast<cv T2*>(static_cast<cv void*>(v))points to the memory address as v if both T1 and T2 are standard-layout types (3.9) and the alignment requirements of T2 are no stricter than those of T1.

That's certainly not perfect (but not worse than many others parts of the standard), but in 2 mn I wrote a better spec than the committee did in a decade.

Jack
  • 10,943
  • 13
  • 50
  • 65
curiousguy
  • 8,038
  • 2
  • 40
  • 58
  • Nice attempt! Have they made any progress on this yet? It's quite sad how something that compiler writers silently implement according to 99% of people's intuition, is still not formally defined anywhere. – underscore_d Jan 07 '16 at 12:57
3

There are some guarantees elsewhere in the standard (see the section on type representation which, IIRC, mandates that corresponding unsigned and signed types share the representation for the common values, still IIRC, there is also some text guaranteeing that you can read anything as characters). But note also that there are some places which lessen even the section you are reading (which states that things are implementation defined and unspecified): some forms of type punning are undefined behavior.

AProgrammer
  • 51,233
  • 8
  • 91
  • 143
2

The standard specifies what has to happen on all platforms, you don't have to do that. If you limit your portability requirements to platforms where your reinterpret_cast actually works, that's fine.

On platforms that actually support a uint16_t, the cast is likely to work. On platforms where char16_t is 18, 24, 32, or 36 bits wide, it might not do the right thing. The question is, do you have to support such platforms? The language standard wants to.

Bo Persson
  • 90,663
  • 31
  • 146
  • 203
0

the result of a pointer-to-pointer reinterpret_cast operation can't safely be used for anything other than being cast back to the original pointer type.

That does not sound right. Assuming sizeof(void *) > sizeof(int *), void *foo; reinterpret_cast<void *>(reinterpret_cast<int *>(foo)) could leave you with a truncated pointer value.

Is it not that reinterpret_cast is — in practice — simply the equivalent to C's default cast?

user611775
  • 1,323
  • 7
  • 11
  • 1
    There is an additional condition in the standard: the intermediate type must have no stronger alignment constraints. – AProgrammer Feb 20 '11 at 14:49
  • 3
    `reinterpret_cast` is definitely not equivalent to C-casts. It can't modify `const` or `volatile` and it can't convert between various function pointer types among other things. – Mark B Feb 20 '11 at 15:21