Does C++ guarantee consistent pointer representations? - c++

Does C++ guarantee consistent pointer representations when a pointer is casted to other pointer types?
For example, does C++ guarantee anything about the following program?
<stdint.h>
struct Foo {};
struct Bar : Foo {};
int main() {
Bar obj;
Foo * a = &obj;
Bar * b = &obj;
void *c = &obj;
#if MAYBE_UB
int * d = reinterpret_cast<int *>(&obj);
#endif
auto aa = reinterpret_cast<uintptr_t>(a);
auto bb = reinterpret_cast<uintptr_t>(b);
auto cc = reinterpret_cast<uintptr_t>(c);
#if MAYBE_UB
auto dd = reinterpret_cast<uintptr_t>(d); // UB? Not reading the pointee...
#endif
if (aa != bb) printf("bb differs\n");
if (aa != cc) printf("cc differs\n");
#if MAYBE_UB
if (aa != dd) printf("dd differs\n");
#endif
return 0;
}

Does C++ guarantee consistent pointer representations?
No, pointers can be represented in any inconsistent way compiler wants them to. Generally, the language tries to be abstract and talk as little as possible about representation of stuff. It's left for the implementation to figure that out.
Does C++ guarantee consistent pointer representations when a pointer is casted to other pointer types?
No, pointers can be represented in any way they want. Using rocks and sticks, for example.
does C++ guarantee anything about the following program?
TL;DR Well, yes, that the code should compile (ignoring the missing #includfe on top). But there is no guarantee about the output.
Aaanyway, I do not think you are interested in the representation of pointers, but if the value of pointer changes when the pointer value is converted to a different type. This has nothing to do with how the value is "represented", the representation of the pointer can change anyhow the compiler wants to. Well, ok, we know char * and void * have the same representation, from https://eel.is/c++draft/basic.compound#5 :
A pointer to cv void can be used to point to objects of unknown type.
Such a pointer shall be able to hold any object pointer.
An object of type “pointer to cv void” shall have the same representation and alignment requirements as an object of type “pointer to cv char”.
Anyway, we know about void * pointer https://eel.is/c++draft/expr#conv.ptr :
A prvalue of type “pointer to cv T”, where T is an object type, can be converted to a prvalue of type “pointer to cv void”.
The pointer value is unchanged by this conversion.
And this also is relevant:
A prvalue of type “pointer to cv D”, where D is a complete class type, can be converted to a prvalue of type “pointer to cv B”, where B is a base class ([class.derived]) of D.
If B is an inaccessible ([class.access]) or ambiguous ([class.member.lookup]) base class of D, a program that necessitates this conversion is ill-formed.
The result of the conversion is a pointer to the base class subobject of the derived class object.
But there is nothing about the value of the result. It can be the same, it can be different, it's not specified.
The problem arises that your code uses uintptr_t. We know that cstdint has to be the same as in C stdint.h, and from C we know only that C99 7.18.1.4:
The following type designates an unsigned integer type with the property that any valid pointer to void can be converted to this type, then converted back to pointer to void, and the result will compare equal to the original pointer:
uintptr_t
We know nothing about the value of (uintptr_t)(Bar*)&obj. We also do not know if two uintptr_t variables compare equal, even when they were created from two equal void pointers. Only that they compare equal when you convert them back. We fall back to general rule at reinterpret_cast https://eel.is/c++draft/expr#reinterpret.cast-4 :
A pointer can be explicitly converted to any integral type large enough to hold all values of its type. The mapping function is implementation-defined.
[Note 2: It is intended to be unsurprising to those who know the addressing structure of the underlying machine.
— end note]
The "mapping function" can be anything. It can produce inconsistent values that depend on anything - it's implementation defined. So, basically, we know nothing about the resulting uintptr_t values, except there should be some values there.
Anyway, from the above, I conclude that all uintptr_t conversions in your code result in an implementation defined value and can have any value the implementation wants them to have. The program has defined, but implementation-defined behavior - any of those ifs can be true or false, depending on the "pointers to integers mapping function" the compiler uses.
However, we know, that any sane compiler will have a sane "mapping function" that maps pointers to integral types, and this mapping functions will be "unsurprising to those who know the addressing structure of the underlying machine" and will produce consistent values. You might be also interested in this pointer provenance.
As for MAYBE_UB, there is nothing undefined in it, we know that https://eel.is/c++draft/expr#reinterpret.cast-7 :
An object pointer can be explicitly converted to an object pointer of a different type. When a prvalue v of object pointer type is converted to the object pointer type “pointer to cv T”, the result is static_­cast<cv T*>(static_­cast<cv void*>(v)).
But we know from https://eel.is/c++draft/expr#static.cast-13 :
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.
If the original pointer value represents the address A of a byte in memory and A does not satisfy the alignment requirement of T, then the resulting pointer value is unspecified.
Otherwise, if the original pointer value points to an object a, and there is an object b of type T (ignoring cv-qualification) that is pointer-interconvertible with a, the result is a pointer to b.
Otherwise, the pointer value is unchanged by the conversion.
The resulting pointer can be unspecified or unchanged, depending on if int satisfies alignment requirements of &obj, but the code is still defined.

Related

What would happen if T1 is not stricter than T2 in reinterpret-casting?

[expr.reinterpret.cast]/7:
Any object pointer type T1* can be
converted to another object pointer type cv T2*. This is exactly
equivalent to static_cast<cv T2*>(static_cast<cv void*>(T1_var))
(which implies that if T2's alignment requirement is not stricter than
T1's, the value of the pointer does not change and conversion of the
resulting pointer back to its original type yields the original value)
Am I wondering that there's no limitation while casting a pointer from one type to another using reinterpret_cast. The only thing I notice, when reading the given quote, is that the target type shall be stricter than the original type, [otherwise] not mentioned in this quote. For example
int *p = 0;
double *q = reinterpret_cast<double *>(p)
Here alignof(int) is not greater (stricter?) than or equal alignof(double). Although the compiler gives no errors or even warnings, I think in this case, q pointer holds an unspecified value? and dereferencing it is undefined behavior.
So does this mean converting a pointer of type T1* into T2*, the resulting pointer always has an unspecified value as long as static_assert(alignof(T1) >= alignof(T2)) fails?
So does this mean converting a pointer of type T1* into T2*, the
resulting pointer always has an unspecified value as long as
static_assert(alignof(T1) >= alignof(T2)) fails?
Not really. The result is only (but always) unspecified if the actual address of the T1 type doesn't fulfil the alignment requirements of the T2 type. It may do so, in which case the behaviour is well-defined.
The paragraph from the Standard you cite states the equivalence of a reinterpret_cast operation to two static_cast operations via an intermediate void* pointer. And, from the Standard, in the previous section about static_cast (bolding for emphasis mine):
8.5.1.9 Static cast     [expr.static.cast]
13     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. If the original pointer value
represents the address A of a byte in memory and A does not satisfy
the alignment requirement of T, then the resulting pointer value is
unspecified. Otherwise, if the original pointer value points to an
object a, and there is an object b of type T (ignoring
cv-qualification) that is pointer-interconvertible (6.7.2) with a, the
result is a pointer to b. Otherwise, the pointer value is unchanged by
the conversion.

Is it UB to access a member by casting an object pointer to `char *`, then doing `*(member_type*)(pointer + offset)`?

Here's an example:
#include <cstddef>
#include <iostream>
struct A
{
char padding[7];
int x;
};
constexpr int offset = offsetof(A, x);
int main()
{
A a;
a.x = 42;
char *ptr = (char *)&a;
std::cout << *(int *)(ptr + offset) << '\n'; // Well-defined or not?
}
I always assumed that it's well-defined (otherwise what would be the point of offsetof), but wasn't sure.
Recently I was told that it's in fact UB, so I want to figure it out once and for all.
Does the example above cause UB or not? If you modify the class to not be standard-layout, does it affect the result?
And if it's UB, are there any workarounds for it (e.g. applying std::launder)?
This entire topic seems to be moot and underspecified.
Here's some information I was able to find:
Is adding to a “char *” pointer UB, when it doesn't actually point to a char array? - In 2011, CWG confirmed that we're allowed to examine the representation of a standard-layout object through an unsigned char pointer.
Unclear if a char pointer can be used insteaed, common sense says it can.
Unclear if staring from C++17 std::launder needs to be applied to the result of the (unsigned char *) cast. Given that it would be a breaking change, it's probably unnecessarly, at least in practice.
Unclear why C++17 changed offsetof to conditionally-support non-standard-layout types (used to be UB). It seems to imply that if an implementation supports that, then it also lets you examine the representation of non-standard-layout objects through unsigned char *.
Do we need to use std::launder when doing pointer arithmetic within a standard-layout object (e.g., with offsetof)? - A question similar to this one. No definitive answer was given.
Here I will refer to C++20 (draft) wording, because one relevant editorial issue was fixed between C++17 and C++20 and also it is possible to refer to specific sentences in HTML version of the C++20 draft, but otherwise there is nothing new in comparison to C++17.
At first, definitions of pointer values [basic.compound]/3:
Every value of pointer type is one of the following:
— a pointer to an object or function (the pointer is said to point to the object or function), or
— a pointer past the end of an object ([expr.add]), or
— the null pointer value for that type, or
— an invalid pointer value.
Now, lets see what happens in the (char *)&a expression.
Let me not prove that a is an lvalue denoting the object of type A, and I will say «the object a» to refer to this object.
The meaning of the &a subexpression is covered in [expr.unary.op]/(3.2):
if the operand is an lvalue of type T, the resulting expression is a prvalue of type “pointer to T” whose result is a pointer to the designated object
So, &a is a prvalue of type A* with the value «pointer to (the object) a».
Now, the cast in (char *)&a is equivalent to reinterpret_cast<char*>(&a), which is defined as static_cast<char*>(static_cast<void*>(&a)) ([expr.reinterpret.cast]/7).
Cast to void* doesn't change the pointer value ([conv.ptr]/2):
A prvalue of type “pointer to cv T”, where T is an object type, can be converted to a prvalue of type “pointer to cv void”. The pointer value ([basic.compound]) is unchanged by this conversion.
i.e. it is still «pointer to (the object) a».
[expr.static.cast]/13 covers the outer static_cast<char*>(...):
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.
If the original pointer value represents the address A of a byte in memory and A does not satisfy the alignment requirement of T, then the resulting pointer value is unspecified.
Otherwise, if the original pointer value points to an object a, and there is an object b of type T (ignoring cv-qualification) that is pointer-interconvertible with a, the result is a pointer to b.
Otherwise, the pointer value is unchanged by the conversion.
There is no object of type char which is pointer-interconvertible with the object a ([basic.compound]/4):
Two objects a and b are pointer-interconvertible if:
— they are the same object, or
— one is a union object and the other is a non-static data member of that object ([class.union]), or
— one is a standard-layout class object and the other is the first non-static data member of that object, or, if the object has no non-static data members, any base class subobject of that object ([class.mem]), or
— there exists an object c such that a and c are pointer-interconvertible, and c and b are pointer-interconvertible.
which means that the static_cast<char*>(...) doesn't change the pointer value and it is the same as in its operand, namely: «pointer to a».
So, (char *)&a is a prvalue of type char* whose value is «pointer to a». This value is stored into char* ptr variable. Then, when you try to do pointer arithmetic with such a value, namely ptr + offset, you step into [expr.add]/6:
For addition or subtraction, if the expressions P or Q have type “pointer to cv T”, where T and the array element type are not similar, the behavior is undefined.
For the purposes of pointer arithmetic, the object a is considered to be an element of an array A[1] ([basic.compound]/3), so the array element type is A, the type of the pointer expression P is «pointer to char», char and A are not similar types (see [conv.qual]/2), so the behavior is undefined.
This question, and the other one about launder, both seem to me to boil down to interpretation of the last sentence of C++17 [expr.static.cast]/13, which covers what happens for static_cast<T *> applied to an operand of pointer to unrelated type which is correctly aligned:
A prvalue of type “pointer to cv1 void” can be converted to a prvalue of type “pointer to cv2 T ”,
[...]
Otherwise, the pointer value is unchanged by the conversion.
Some posters appear to take this to mean that the result of the cast cannot point to an object of type T, and consequently that reinterpret_cast with pointers or references can only be used on pointer-interconvertible types.
But I don't see that as justified, and (this is a reductio ad absurdum argument) that position would also imply:
The resolution to CWG1314 is overturned.
Inspecting any byte of a standard-layout object is not possible (since casting to unsigned char * or whatever character type supposedly cannot be used to access that byte).
The strict aliasing rule would be redundant since the only way to actually achieve such aliasing is to use such casts.
There would be no normative text to justify the note "[Note: Converting a prvalue of type “pointer to T1 ” to the type “pointer to T2 ” (where T1 and T2 are object types and where the alignment requirements of T2 are no stricter than those of T1) and back to its original type yields the original pointer value. —end note ]"
offsetof is useless (and the C++17 changes to it were therefore redundant)
It seems like a more sensible interpretation to me that this sentence means the result of the cast points to the same byte in memory as the operand. (As opposed to pointing to some other byte, as can happen for some pointer casts not covered by this sentence). Saying "the value is unchanged" does not mean "the type is unchanged", e.g. we describe conversion from int to long as preserving the value.
Also, I guess this may be controversial to some but I am taking as axiomatic that if a pointer's value is the address of an object, then the pointer points to that object, unless the Standard specifically excludes the case.
This is consistent with the text of [basic.compound]/3 which says the converse, i.e. that if a pointer points to an object, then its value is the address of the object.
There doesn't seem to be any other explicit statement defining when a pointer can or cannot be said to point to an object, but basic.compound/3 says that all pointers must be one of four cases (points to an object, points past the end, null, invalid).
Examples of excluded cases include:
The use case of std::launder specifically addresses a situation where there was such language ruling out the use of the un-laundered pointer.
A past-the-end pointer does not point to an object. (basic.compound/3)

How to cast a pointer to char to a pointer to int

I want to cast a pointer pc which points to char to a point pi which points to int
char *pc;
int *pi;
pi = (int*)pc // compiler complaint about old-style cast
pi = static_cast<int *>(static_cast<void *>(pc)) // no complaint any more but too complex
is there any simpler ways to do this cast and make compiler silence?
If you really need to do this then reinterpret_cast is your friend:
char *pc = 0;
int *pi = 0;
pi = reinterpret_cast<int*>(pc);
The behaviour on converting, in general, a char* pointer to an int* pointer is undefined. Dereferencing such a pointer will cause you further trouble as you will be breaking strict aliasing rules. Note that the C++ Standard does not require sizeof(char*) to be the same as sizeof(int*).
(Note that converting an unsigned char* pointer to an int* is well-defined if the unsigned char* pointer actually points to an int).
Don't do it. Ever.
I want to put the back and forth under #Bathsheba's post to rest. So here's an answer about the finer details of what you are doing.
#Sean already suggested you reinterpret_cast your pointers instead. And that is equivalent to your second chain of casts. It says as much in [expr.reinterpret.cast]/7:
An object pointer can be explicitly converted to an object pointer of
a different type. When a prvalue v of object pointer type is
converted to the object pointer type “pointer to cv T”, the result is
static_­cast<cv T*>(static_­cast<cv void*>(v)). [ Note: Converting a
prvalue of type “pointer to T1” to the type “pointer to T2” (where T1
and T2 are object types and where the alignment requirements of T2 are
no stricter than those of T1) and back to its original type yields the
original pointer value.  — end note ]
Now, let's examine each step of the two step conversion. First we have a static_cast<void*>. According to [conv.ptr]/2 (emphasis mine):
A prvalue of type “pointer to cv T”, where T is an object type, can be
converted to a prvalue of type “pointer to cv void”. The pointer value
is unchanged by this conversion.
The first conversion doesn't do any alteration to the address. And then it also says in [basic.compound]/5:
A pointer to cv-qualified or cv-unqualified void can be used to point
to objects of unknown type. Such a pointer shall be able to hold any
object pointer. An object of type cv void* shall have the same
representation and alignment requirements as cv char*.
So a char* may store any address a void* may store. Now it doesn't mean the conversion from void* to char* is value preserving, only that they can represent the same values. Now, assuming a very restricted use case, that is enough of a guarantee. But there's more at [expr.static.cast]/13:
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. If
the original pointer value represents the address A of a byte in
memory and A does not satisfy the alignment requirement of T, then the
resulting pointer value is unspecified. Otherwise, if the original
pointer value points to an object a, and there is an object b of type
T (ignoring cv-qualification) that is pointer-interconvertible with a,
the result is a pointer to b. Otherwise, the pointer value is
unchanged by the conversion.
Where am I going with this? Assuming pc already holds the address of an int (suitably converted according to the above), then casting the char* to an int* via reinterpret_cast will give you the address of the original int. The note under the first paragraph says as much, and the further quotes prove it. If it doesn't hold the address of an int, you are playing roulette and are likely going to lose. Your program has undefined behavior. You should follow Bathsheba's advice to the letter.

Reshaping a 1-d array to a multidimensional array

Taking into consideration the entire C++11 standard, is it possible for any conforming implementation to succeed the first assertion below but fail the latter?
#include <cassert>
int main(int, char**)
{
const int I = 5, J = 4, K = 3;
const int N = I * J * K;
int arr1d[N] = {0};
int (&arr3d)[I][J][K] = reinterpret_cast<int (&)[I][J][K]>(arr1d);
assert(static_cast<void*>(arr1d) ==
static_cast<void*>(arr3d)); // is this necessary?
arr3d[3][2][1] = 1;
assert(arr1d[3 * (J * K) + 2 * K + 1] == 1); // UB?
}
If not, is this technically UB or not, and does that answer change if the first assertion is removed (is reinterpret_cast guaranteed to preserve addresses here?)? Also, what if the reshaping is done in the opposite direction (3d to 1d) or from a 6x35 array to a 10x21 array?
EDIT: If the answer is that this is UB because of the reinterpret_cast, is there some other strictly compliant way of reshaping (e.g., via static_cast to/from an intermediate void *)?
Update 2021-03-20:
This same question was asked on Reddit recently and it was pointed out that my original answer is flawed because it does not take into account this aliasing rule:
If a program attempts to access the stored value of an object through a glvalue whose type is not similar to one of the following types the behavior is undefined:
the dynamic type of the object,
a type that is the signed or unsigned type corresponding to the dynamic type of the object, or
a char, unsigned char, or std​::​byte type.
Under the rules for similarity, these two array types are not similar for any of the above cases and therefore it is technically undefined behaviour to access the 1D array through the 3D array. (This is definitely one of those situations where, in practice, it will almost certainly work with most compilers/targets)
Note that the references in the original answer refer to an older C++11 draft standard
Original answer:
reinterpret_cast of references
The standard states that an lvalue of type T1 can be reinterpret_cast to a reference to T2 if a pointer to T1 can be reinterpret_cast to a pointer to T2 (§5.2.10/11):
An lvalue expression of type T1 can be cast to the type “reference to T2” if an expression of type “pointer to T1” can be explicitly converted to the type “pointer to T2” using a reinterpret_cast.
So we need to determine if a int(*)[N] can be converted to an int(*)[I][J][K].
reinterpret_cast of pointers
A pointer to T1 can be reinterpret_cast to a pointer to T2 if both T1 and T2 are standard-layout types and T2 has no stricter alignment requirements than T1 (§5.2.10/7):
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, or if either type is void.
Are int[N] and int[I][J][K] standard-layout types?
int is a scalar type and arrays of scalar types are considered to be standard-layout types (§3.9/9).
Scalar types, standard-layout class types (Clause 9), arrays of such types and cv-qualified versions of these types (3.9.3) are collectively called standard-layout types.
Does int[I][J][K] have no stricter alignment requirements than int[N].
The result of the alignof operator gives the alignment requirement of a complete object type (§3.11/2).
The result of the alignof operator reflects the alignment requirement of the type in the complete-object case.
Since the two arrays here are not subobjects of any other object, they are complete objects. Applying alignof to an array gives the alignment requirement of the element type (§5.3.6/3):
When alignof is applied to an array type, the result shall be the alignment of the element type.
So both array types have the same alignment requirement.
That makes the reinterpret_cast valid and equivalent to:
int (&arr3d)[I][J][K] = *reinterpret_cast<int (*)[I][J][K]>(&arr1d);
where * and & are the built-in operators, which is then equivalent to:
int (&arr3d)[I][J][K] = *static_cast<int (*)[I][J][K]>(static_cast<void*>(&arr1d));
static_cast through void*
The static_cast to void* is allowed by the standard conversions (§4.10/2):
A prvalue of type “pointer to cv T,” where T is an object type, can be converted to a prvalue of type “pointer to cv void”. The result of converting a “pointer to cv T” to a “pointer to cv void” points to the start of the storage location where the object of type T resides, as if the object is a most derived object (1.8) of type T (that is, not a base class subobject).
The static_cast to int(*)[I][J][K] is then allowed (§5.2.9/13):
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.
So the cast is fine! But are we okay to access objects through the new array reference?
Accessing array elements
Performing array subscripting on an array like arr3d[E2] is equivalent to *((E1)+(E2)) (§5.2.1/1). Let's consider the following array subscripting:
arr3d[3][2][1]
Firstly, arr3d[3] is equivalent to *((arr3d)+(3)). The lvalue arr3d undergoes array-to-pointer conversion to give a int(*)[2][1]. There is no requirement that the underlying array must be of the correct type to do this conversion. The pointers value is then accessed (which is fine by §3.10) and then the value 3 is added to it. This pointer arithmetic is also fine (§5.7/5):
If both the pointer operand and the result point to elements of the same array object, or one past the last element of the array object, the evaluation shall not produce an overflow; otherwise, the behavior is undefined.
This this pointer is dereferenced to give an int[2][1]. This undergoes the same process for the next two subscripts, resulting in the final int lvalue at the appropriate array index. It is an lvalue due to the result of * (§5.3.1/1):
The unary * operator performs indirection: the expression to which it is applied shall be a pointer to an object type, or a pointer to a function type and the result is an lvalue referring to the object or function to which the expression points.
It is then perfectly fine to access the actual int object through this lvalue because the lvalue is of type int too (§3.10/10):
If a program attempts to access the stored value of an object through a glvalue of other than one of the following types the behavior is undefined:
the dynamic type of the object
[...]
So unless I've missed something. I'd say this program is well-defined.
I am under the impression that it will work. You allocate the same piece of contiguous memory. I know the C-standard guarantees it will be contiguous at least. I don't know what is said in the C++11 standard.
However the first assert should always be true. The address of the first element of the array will always be the same. All memory address will be the same since the same piece of memory is allocated.
I would therefore also say that the second assert will always hold true. At least as long as the ordering of the elements are always in row major order. This is also guaranteed by the C-standard and I would be surprised if the C++11 standard says anything differently.

casting via void* instead of using reinterpret_cast [duplicate]

This question already has answers here:
Should I use static_cast or reinterpret_cast when casting a void* to whatever
(9 answers)
Closed 1 year ago.
I'm reading a book and I found that reinterpret_cast should not be used directly, but rather casting to void* in combination with static_cast:
T1 * p1=...
void *pv=p1;
T2 * p2= static_cast<T2*>(pv);
Instead of:
T1 * p1=...
T2 * p2= reinterpret_cast<T2*>(p1);
However, I can't find an explanation why is this better than the direct cast. I would very appreciate if someone can give me an explanation or point me to the answer.
Thanks in advance
p.s. I know what is reinterpret_cast used for, but I never saw that is used in this way
For types for which such cast is permitted (e.g. if T1 is a POD-type and T2 is unsigned char), the approach with static_cast is well-defined by the Standard.
On the other hand, reinterpret_cast is entirely implementation-defined - the only guarantee that you get for it is that you can cast a pointer type to any other pointer type and then back, and you'll get the original value; and also, you can cast a pointer type to an integral type large enough to hold a pointer value (which varies depending on implementation, and needs not exist at all), and then cast it back, and you'll get the original value.
To be more specific, I'll just quote the relevant parts of the Standard, highlighting important parts:
5.2.10[expr.reinterpret.cast]:
The mapping performed by reinterpret_cast is implementation-defined. [Note: it might, or might not, produce a representation different from the original value.] ... A pointer to an object can be explicitly converted to a pointer to an object of different type.) Except that converting an rvalue of type “pointer to T1” to the type “pointer to T2” (where T1 and T2 are object types and where the alignment requirements of T2 are no stricter than those of T1) and back to its original type yields the original pointer value, the result of such a pointer conversion is unspecified.
So something like this:
struct pod_t { int x; };
pod_t pod;
char* p = reinterpret_cast<char*>(&pod);
memset(p, 0, sizeof pod);
is effectively unspecified.
Explaining why static_cast works is a bit more tricky. Here's the above code rewritten to use static_cast which I believe is guaranteed to always work as intended by the Standard:
struct pod_t { int x; };
pod_t pod;
char* p = static_cast<char*>(static_cast<void*>(&pod));
memset(p, 0, sizeof pod);
Again, let me quote the sections of the Standard that, together, lead me to conclude that the above should be portable:
3.9[basic.types]:
For any object (other than a base-class subobject) of POD type T, whether or not the object holds a valid value of type T, the underlying bytes (1.7) making up the object can be copied into an array of char or unsigned char. If the content of the array of char or unsigned char is copied back into the object, the object shall subsequently hold its original value.
The object representation of an object of type T is the sequence of N unsigned char objects taken up by the object of type T, where N equals sizeof(T).
3.9.2[basic.compound]:
Objects of cv-qualified (3.9.3) or cv-unqualified type void* (pointer to void), can be used to point to objects of unknown type. A void* shall be able to hold any object pointer. A cv-qualified or cv-unqualified (3.9.3) void* shall have the same representation and alignment requirements as a cv-qualified or cv-unqualified char*.
3.10[basic.lval]:
If a program attempts to access the stored value of an object through an lvalue of other than one of the following types the behavior is undefined):
...
a char or unsigned char type.
4.10[conv.ptr]:
An rvalue of type “pointer to cv T,” where T is an object type, can be converted to an rvalue of type “pointer to cv void.” The result of converting a “pointer to cv T” to a “pointer to cv void” points to the start of the storage location where the object of type T resides, as if the object is a most derived object (1.8) of type T (that is, not a base class subobject).
5.2.9[expr.static.cast]:
The inverse of any standard conversion sequence (clause 4), other than the lvalue-to-rvalue (4.1), array-topointer (4.2), function-to-pointer (4.3), and boolean (4.12) conversions, can be performed explicitly using static_cast.
[EDIT] On the other hand, we have this gem:
9.2[class.mem]/17:
A pointer to a POD-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. [Note: There might therefore be unnamed padding within a POD-struct object, but not at its beginning, as necessary to achieve appropriate alignment. ]
which seems to imply that reinterpret_cast between pointers somehow implies "same address". Go figure.
There is not the slightest doubt that the intent is that both forms are well defined, but the wording fails to capture that.
Both forms will work in practice.
reinterpret_cast is more explicit about the intent and should be preferred.
The real reason this is so is because of how C++ defines inheritance, and because of member pointers.
With C, pointer is pretty much just an address, as it should be. In C++ it has to be more complex because of some of its features.
Member pointers are really an offset into a class, so casting them is always a disaster using C style.
If you have multiply inherited two virtual objects that also have some concrete parts, that's also a disaster for C style. This is the case in multiple inheritance that causes all the problems, though, so you should not ever want to use this anyway.
Really hopefully you never use these cases in the first place. Also, if you are casting a lot that's another sign you are messing up in in your design.
The only time I end up casting is with the primitives in areas C++ decides are not the same but where obviously they have to be. For actual objects, any time you want to cast something, start to question your design because you should be 'programming to the interface' most of the time. Of course, you can't change how 3rd party APIs work so you don't always have much choice.