Pointer arithmetics on non-array types - c++

Let's consider following piece of code:
struct Blob {
double x, y, z;
} blob;
char* s = reinterpret_cast<char*>(&blob);
s[2] = 'A';
Assuming that sizeof(double) is 8, does this code trigger undefined behaviour?

Quoting from N4140 (roughly C++14):
3.9 Types [basic.types]
2 For any object (other than a base-class subobject) of trivially copyable 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.42 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.
42) By using, for example, the library functions (17.6.1.2) std::memcpy or std::memmove.
3 For any trivially copyable type T, if two pointers to T point to distinct T objects obj1 and obj2, where neither obj1 nor obj2 is a base-class subobject, if the underlying bytes (1.7) making up obj1 are copied
into obj2,43 obj2 shall subsequently hold the same value as obj1. [ Example: ... ]
43) By using, for example, the library functions (17.6.1.2) std::memcpy or std::memmove.
This does, in principle, allow assignment directly to s[2] if you take the position that assignment to s[2] is indirectly required to be equivalent to copying all of some other Blob into an array that just happens to be bytewise identical except for the third byte, and copying it into your Blob: you're not assigning to s[0], s[1], etc. For trivially copyable types including char, that is equivalent to setting them to the exact value they already have, which also has no observable effect.
However, if the only way to get s[2] == 'A' is by memory manipulation, then a valid argument could also be made that what you're copying back into your Blob isn't the underlying bytes that made up any previous Blob. In that case, technically, the behaviour would be undefined by omission.
I do strongly suspect, especially given the "whether or not the object holds a valid value of type T" comment, that it's intended to be allowed.

Chapter 3.10 of the standard seems to allow for that specific case, assuming that "access the stored value" means "read or write", which is unclear.
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:
—(10.1) the dynamic type of the object,
—(10.2) a cv-qualified version of the dynamic type of the object,
—(10.3) a type similar (as defined in 4.4) to the dynamic type of the
object,
—(10.4) a type that is the signed or unsigned type corresponding to
the dynamic type of the object,
—(10.5) a type that is the signed or unsigned type corresponding to a
cv-qualified version of the dynamic type of the object,
—(10.6) an aggregate or union type that includes one of the
aforementioned types among its elements or nonstatic data members
(including, recursively, an element or non-static data member of a
subaggregate or contained union),
—(10.7) a type that is a (possibly cv-qualified) base class type of the
dynamic type of the object,
—(10.8) a char or unsigned char type.

Related

UB When Dereferencing Array of Unions

Which of these are undefined behaviour:
template <class T> struct Struct { T t; };
template <class T> union Union { T t; };
template <class T> void function() {
Struct aS[10];
Union aU[10];
// do something with aS[9].t and aU[9].t including initialization
T *aSP = reinterpret_cast<T *>(aS);
T *aUP = reinterpret_cast<T *>(aU);
// so here is this undefined behaviour?
T valueS = aSP[9];
// use valueS in whatever way
// so here is this undefined behaviour?
T valueU = aUP[9];
// use valueU in whatever way
// now is accessing aS[9].t or aU[9].t now UB?
}
So yeah, which of the last 3 operations is UB?
(My reasoning: I don't know about the struct, if there is any requirement for its size to be the same as its single element, but AFAIK the union has to be the same size as the element. Alignment requirements I don't know for the union, but I am guessing it is the same. For the struct I have no idea. In the case of the union I would guess that it is not UB, but as I said, I am really really not sure. For the struct I actually have no idea)
tl;dr: the last two statements in your code above will always invoke undefined behavior, simply casting a pointer to a union to a pointer to one of its member types is generally fine because it doesn't really do anything (it's unspecified at worst, but never undefined behavior; note: we're talking about just the cast itself, using the result of the cast to access an object is a whole different story).
Depending on what T ends up being, Struct<T> may potentially be a standard-layout struct [class.prop]/3 in which case
T *aSP = reinterpret_cast<T *>(aS);
would be well-defined because a Struct<T> would be pointer-interconvertible with its first member (which is of type T) [basic.compound]/4.3. Above reinterpret_cast is equivalent to [expr.reinterpret.cast]/7
T *aSP = static_cast<T *>(static_cast<void *>(aS));
which will invoke the array-to-pointer conversion [conv.array], resulting in a Struct<T>* pointing to the first element of aS. This pointer is then converted to void* (via [expr.static.cast]/4 and [conv.ptr]/2), which is then converted to T*, which would be legal via [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.
Similarly,
T *aUP = reinterpret_cast<T *>(aU);
would be well-defined in C++17 if Union<T> is a standard-layout union and looks to be well-defined in general with the coming version of C++ based on the current standard draft, where a union and one of its members are always pointer-interconvertible [basic.compound]/4.2
All of the above is irrelevant, however, because
T valueS = aSP[9];
and
T valueU = aUP[9];
will invoke undefined behavior no matter what. aSP[9] and aUP[9] are (by definition) the same as *(aSP + 9) and *(aUP + 9) respectively [expr.sub]/1. The pointer arithmetic in these expressions is subject to [expr.add]/4
When an expression J that has integral type is added to or subtracted from an expression P of pointer type, the result has the type of P.
If P evaluates to a null pointer value and J evaluates to 0, the result is a null pointer value.
Otherwise, if P points to element x[i] of an array object x with n elements, the expressions P + J and J + P (where J has the value j) point to the (possibly-hypothetical) element x[i+j] if 0≤i+j≤n and the expression P - J points to the (possibly-hypothetical) element x[i−j] if 0≤i−j≤n.
Otherwise, the behavior is undefined.
aSP and aUP do not point to an element of an array. Even if aSP and aUP would be pointer-interconvertible with T, you'd only ever be allowed to access element 0 and compute the address of (but not access) element 1 of the hypothetical single-element array…
So if we look at the doc of reinterpret_cast (here)
5) Any object pointer type T1* can be converted to another object
pointer type cv T2*. This is exactly equivalent to static_cast(static_cast(expression)) (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). In any case, the
resulting pointer may only be dereferenced safely if allowed by the
type aliasing rules (see below)
Now What say the aliasing rules ?
Whenever an attempt is made to read or modify the stored value of an
object of type DynamicType through a glvalue of type AliasedType, the
behavior is undefined unless one of the following is true:
AliasedType and DynamicType are similar.
AliasedType is the (possibly cv-qualified) signed or unsigned variant of DynamicType.
AliasedType is std::byte, (since C++17)char, or unsigned char: this permits examination of the object representation of any object as
an array of bytes.
So it's not 2 nor 3. May be 1?
Similar:
Informally, two types are similar if, ignoring top-level
cv-qualification:
they are the same type; or
they are both pointers, and the pointed-to types are similar; or
they are both pointers to member of the same class, and the types of the pointed-to members are similar; or
they are both arrays of the same size or both arrays of unknown bound, and the array element types are similar.
And, from C++17 draft:
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.
If two objects are pointer-interconvertible, then they have the same
address, and it is possible to obtain a pointer to one from a pointer
to the other via a reinterpret_­cast. [ Note: An array object and its
first element are not pointer-interconvertible, even though they have
the same address. — end note]
So, for me :
T *aSP = reinterpret_cast<T *>(aS); // Is OK
T *aUP = reinterpret_cast<T *>(aU); // Is OK.
I found c++ - Is sizeof(T) == sizeof(int). This specifies that structs do not have to have the same size as their elements (sigh). As for unions, the same would probably apply (after reading the answers, I am led to believe so). This is alone necessary to make this situation UB. However, if sizeof(Struct) == sizeof(T), and "It's well-established that" in https://stackoverflow.com/a/21515546, a pointer to aSP[9] would be the same location as that of aS[9] (at least I think so), and reinterpret_cast'ing that is guarantied by the standard (according to the quote in https://stackoverflow.com/a/21509729).
EDIT: This is actually wrong. The correct answer is here.

Has a std::byte pointer the same aliasing implications as char*?

C++ (and C) strict aliasing rules include that a char* and unsigned char* may alias any other pointer.
AFAIK there is no analogous rule for uint8_t*.
Thus my question: What are the aliasing rules for a std::byte pointer?
The C++ reference currently just specifies:
Like the character types (char, unsigned char, signed char) it can be used to access raw memory occupied by other objects (object representation), but unlike those types, it is not a character type and is not an arithmetic type.
From the current Standard draft ([basic.types]/2):
For any object (other than a base-class subobject) of trivially
copyable type T, whether or not the object holds a valid value of type
T, the underlying bytes ([intro.memory]) making up the object can be
copied into an array of char, unsigned char, or std​::​byte
([cstddef.syn]).43 If the content of that array is copied back into
the object, the object shall subsequently hold its original value.
So yes, the same aliasing rules apply for the three types, just as cppreference sums up.
It also might be valuable to mention ([basic.lval]/8.8):
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:
a char, unsigned char, or std​::​byte type.

Why can't I assign the address of a variable of one type(say double) to a pointer of int type?

In case of pointers, we know that their size is always same irrespective of data type of the variable it is pointing.
Data type is needed when dereferencing the pointer so it knows how much data it should read. So why cant i assign address of variable of double type to a pointer of int type?
why cant it happen like dereferencing a int pointer reads next 4 bytes from variable of double type and print its value?
Many computers have alignment requirements, so (for example) to read a 2-byte value, the address at which it's located must be a multiple of 2 (and likewise, a 4-byte value must be located at an address that's a multiple of 4, and so on). In fact, this alignment requirement is common enough that it's frequently referred to as "natural alignment".
Likewise, some types (e.g., floating point types) impose requirements on the bit sequence that can be read as that type, so if you try to take some arbitrary data and treat it as a double, you might trigger something like a floating point exception.
If you want to do this badly enough, you can use a cast to turn the pointer into the target type (but the results, if any, aren't usually portable).
You are guaranteed that you can convert a pointer to any other type of object to a pointer to unsigned char, and use that to read the bytes that represent the pointee object.
Also, if you primarily want an opaque pointer, without type information attached, you can assign a pointer to some other type to a void *.
Finally: no, not all pointers are actually the same. Pointers to different types can be different sizes (e.g., on the early Cray compilers, a char * was substantially different from an int *).
In case of pointers, we know that their size is always same irrespective of data type of the variable it is pointing.
No, we do not know that.
Chapter and verse for C
6.2.5 Types
...
28 A pointer to void shall have the same representation and alignment requirements as a
pointer to a character type.48) Similarly, pointers to qualified or unqualified versions of
compatible types shall have the same representation and alignment requirements. All
pointers to structure types shall have the same representation and alignment requirements
as each other. All pointers to union types shall have the same representation and
alignment requirements as each other. Pointers to other types need not have the same
representation or alignment requirements.
48) The same representation and alignment requirements are meant to imply interchangeability as
arguments to functions, return values from functions, and members of unions.
Emphasis added.
Chapter and verse for C++
3.9.2 Compound types
...
3 The type of a pointer to void or a pointer to an object type is called an object pointer type. [ Note: A pointer
to void does not have a pointer-to-object type, however, because void is not an object type. — end note ]
The type of a pointer that can designate a function is called a function pointer type. A pointer to objects
of type T is referred to as a “pointer to T.” [Example: a pointer to an object of type int is referred to as
“pointer to int ” and a pointer to an object of class X is called a “pointer to X.” — end example ] Except
for pointers to static members, text referring to “pointers” does not apply to pointers to members. Pointers
to incomplete types are allowed although there are restrictions on what can be done with them (3.11).
A valid value of an object pointer type represents either the address of a byte in memory (1.7) or a null
pointer (4.10). If an object of type T is located at an address A, a pointer of type cv T* whose value is the
address A is said to point to that object, regardless of how the value was obtained. [ Note: For instance,
the address one past the end of an array (5.7) would be considered to point to an unrelated object of the
array’s element type that might be located at that address. There are further restrictions on pointers to
objects with dynamic storage duration; see 3.7.4.3. — end note ] The value representation of pointer types
is implementation-defined. Pointers to layout-compatible types shall have the same value representation and
alignment requirements (3.11). [ Note: Pointers to over-aligned types (3.11) have no special representation,
but their range of valid values is restricted by the extended alignment requirement. This International
Standard specifies only two ways of obtaining such a pointer: taking the address of a valid object with
an over-aligned type, and using one of the runtime pointer alignment functions. An implementation may
provide other means of obtaining a valid pointer value for an over-aligned type. — end note ]
4 A pointer to cv-qualified (3.9.3) 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*.
Emphasis added. It is entirely possible to have different sizes and representations for different pointer types. There is no reason to expect a pointer to int to have the same size and representation as a pointer to double, or a pointer to a struct type, or a pointer to a function type. It's true for commodity platforms like x86, but not all the world runs on x86.
This is why you can't assign pointer values of one type to pointer values of another type without an explicit cast (except for converting between void * and other pointer types in C), since a representation change may be required.
Secondly, pointer arithmetic depends on the size of the pointed-to type. Assume you have pointers to a 32-bit int and a 64-bit double:
int *ip;
double *dp;
The expression ip + 1 will return the address of the next integer object (current address plus 4), while the expression dp + 1 will return the address of the next double object (current address plus 8).
If I assign the address of a double to a pointer to int, incrementing that int pointer won't take me to the next double object.

Is memcpy of a trivially-copyable type construction or assignment?

Let's say you have an object of type T and a suitably-aligned memory buffer alignas(T) unsigned char[sizeof(T)]. If you use std::memcpy to copy from the object of type T to the unsigned char array, is that considered copy construction or copy-assignment?
If a type is trivially-copyable but not standard-layout, it is conceivable that a class such as this:
struct Meow
{
int x;
protected: // different access-specifier means not standard-layout
int y;
};
could be implemented like this, because the compiler isn't forced into using standard-layout:
struct Meow_internal
{
private:
ptrdiff_t x_offset;
ptrdiff_t y_offset;
unsigned char buffer[sizeof(int) * 2 + ANY_CONSTANT];
};
The compiler could store x and y of Meow within buffer at any portion of buffer, possibly even at a random offset within buffer, so long as they are aligned properly and do not overlap. The offset of x and y could even vary randomly with each construction if the compiler wishes. (x could go after y if the compiler wishes because the Standard only requires members of the same access-specifier to go in order, and x and y have different access-specifiers.)
This would meet the requirements of being trivially-copyable; a memcpy would copy the hidden offset fields, so the new copy would work. But some things would not work. For example, holding a pointer to x across a memcpy would break:
Meow a;
a.x = 2;
a.y = 4;
int *px = &a.x;
Meow b;
b.x = 3;
b.y = 9;
std::memcpy(&a, &b, sizeof(a));
++*px; // kaboom
However, is the compiler really allowed to implement a trivially-copyable class in this manner? Dereferencing px should only be undefined behavior if a.x's lifetime has ended. Has it? The relevant portions of the N3797 draft Standard aren't very clear on the subject. This is section [basic.life]/1:
The lifetime of an object is a runtime property of the object. An
object is said to have non-trivial initialization if it is of a class
or aggregate type and it or one of its members is initialized by a
constructor other than a trivial default constructor. [ Note:
initialization by a trivial copy/move constructor is non-trivial
initialization. — end note ] The lifetime of an object of type T
begins when:
storage with the proper alignment and size for type T is obtained, and
if the object has non-trivial initialization, its initialization is complete.
The lifetime of an object of type T ends when:
if T is a class type with a non-trivial destructor ([class.dtor]), the destructor call starts, or
the storage which the object occupies is reused or released.
And this is [basic.types]/3:
For any object (other than a base-class subobject) of trivially
copyable type T, whether or not the object holds a valid value of
type T, the underlying bytes ([intro.memory]) 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. example omitted
The question then becomes, is a memcpy overwrite of a trivially-copyable class instance "copy construction" or "copy-assignment"? The answer to the question seems to decide whether Meow_internal is a valid way for a compiler to implement trivially-copyable class Meow.
If memcpy is "copy construction", then the answer is that Meow_internal is valid, because copy construction is reusing the memory. If memcpy is "copy-assignment", then the answer is that Meow_internal is not a valid implementation, because assignment does not invalidate pointers to the instantiated members of a class. If memcpy is both, I have no idea what the answer is.
It is clear to me that using std::memcpy results in neither construction nor assignment. It is not construction, since no constructor will be called. Nor is it assignment, as the assignment operator will not be called. Given that a trivially copyable object has trivial destructors, (copy/move) constructors, and (copy/move) assignment operators, the point is rather moot.
You seem to have quoted ¶2 from §3.9 [basic.types]. On ¶3, it states:
For any trivially copyable type T, if two pointers to T point to distinct T objects obj1 and obj2, where neither obj1 nor obj2 is a base-class subobject, if the underlying bytes (1.7) making up obj1 are copied into obj2,41 obj2 shall subsequently hold the same value as obj1. [ Example:
T* t1p;
T* t2p;
// provided that t2p points to an initialized object ...
std::memcpy(t1p, t2p, sizeof(T));
// at this point, every subobject of trivially copyable type in *t1p contains
// the same value as the corresponding subobject in *t2p
— end example ]
41) By using, for example, the library functions (17.6.1.2) std::memcpy or std::memmove.
Clearly, the standard intended to allow *t1p to be useable in every way *t2p would be.
Continuing on to ¶4:
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). The value representation of an object is the set of bits that hold the value of type T. For trivially copyable types, the value representation is a set of bits in the object representation that determines a value, which is one discrete element of an implementation-defined set of values.42
42) The intent is that the memory model of C++ is compatible with that of ISO/IEC 9899 Programming Language C.
The use of the word the in front of both defined terms implies that any given type only has one object representation and a given object has only one value representation. Your hypothetical morphing internal type should not exist. The footnote makes it clear that the intention is for trivially copyable types to have a memory layout compatible with C. The expectation is then that even an object with non-standard layout, copying it around will still allow it to be useable.
In the same draft, you also find the following text, directly following the text you quoted:
For any trivially copyable type T, if two pointers to T point to distinct T objects obj1 and obj2, where
neither obj1 nor obj2 is a base-class subobject, if the underlying bytes (1.7) making up obj1 are copied
into obj2, obj2 shall subsequently hold the same value as obj1.
Note that this speaks about a change of the value of obj2, not about destroying the object obj2 and creating a new object in its place. Since not the object, but only its value is changed, any pointers or references to its members should therefore remain valid.

Aliasing T* with char* is allowed. Is it also allowed the other way around?

Note: This question has been renamed and reduced to make it more focused and readable. Most of the comments refer to the old text.
According to the standard, objects of different type may not share the same memory location. So this would not be legal:
std::array<short, 4> shorts;
int* i = reinterpret_cast<int*>(shorts.data()); // Not OK
The standard, however, allows an exception to this rule: any object may be accessed through a pointer to char or unsigned char:
int i = 0;
char * c = reinterpret_cast<char*>(&i); // OK
However, it is not clear to me whether this is also allowed the other way around. For example:
char * c = read_socket(...);
unsigned * u = reinterpret_cast<unsigned*>(c); // huh?
Some of your code is questionable due to the pointer conversions involved. Keep in mind that in those instances reinterpret_cast<T*>(e) has the semantics of static_cast<T*>(static_cast<void*>(e)) because the types that are involved are standard-layout. (I would in fact recommend that you always use static_cast via cv void* when dealing with storage.)
A close reading of the Standard suggests that during a pointer conversion to or from T* it is assumed that there really is an actual object T* involved -- which is hard to fulfill in some of your snippet, even when 'cheating' thanks to the triviality of types involved (more on this later). That would be besides the point however because...
Aliasing is not about pointer conversions. This is the C++11 text that outlines the rules that are commonly referred to as 'strict aliasing' rules, from 3.10 Lvalues and rvalues [basic.lval]:
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,
a cv-qualified version of the dynamic type of the object,
a type similar (as defined in 4.4) to the dynamic type of the object,
a type that is the signed or unsigned type corresponding to the dynamic type of the object,
a type that is the signed or unsigned type corresponding to a cv-qualified version of the dynamic type of the object,
an aggregate or union type that includes one of the aforementioned types among its elements or non-static data members (including, recursively, an element or non-static data member of a subaggregate or contained union),
a type that is a (possibly cv-qualified) base class type of the dynamic type of the object,
a char or unsigned char type.
(This is paragraph 15 of the same clause and subclause in C++03, with some minor changes in the text with e.g. 'lvalue' being used instead of 'glvalue' since the latter is a C++11 notion.)
In the light of those rules, let's assume that an implementation provides us with magic_cast<T*>(p) which 'somehow' converts a pointer to another pointer type. Normally this would be reinterpret_cast, which yields unspecified results in some cases, but as I've explained before this is not so for pointers to standard-layout types. Then it's plainly true that all of your snippets are correct (substituting reinterpret_cast with magic_cast), because no glvalues are involved whatsoever with the results of magic_cast.
Here is a snippet that appears to incorrectly use magic_cast, but which I will argue is correct:
// assume constexpr max
constexpr auto alignment = max(alignof(int), alignof(short));
alignas(alignment) char c[sizeof(int)];
// I'm assuming here that the OP really meant to use &c and not c
// this is, however, inconsequential
auto p = magic_cast<int*>(&c);
*p = 42;
*magic_cast<short*>(p) = 42;
To justify my reasoning, assume this superficially different snippet:
// alignment same as before
alignas(alignment) char c[sizeof(int)];
auto p = magic_cast<int*>(&c);
// end lifetime of c
c.~decltype(c)();
// reuse storage to construct new int object
new (&c) int;
*p = 42;
auto q = magic_cast<short*>(p);
// end lifetime of int object
p->~decltype(0)();
// reuse storage again
new (p) short;
*q = 42;
This snippet is carefully constructed. In particular, in new (&c) int; I'm allowed to use &c even though c was destroyed due to the rules laid out in paragraph 5 of 3.8 Object lifetime [basic.life]. Paragraph 6 of same gives very similar rules to references to storage, and paragraph 7 explains what happens to variables, pointers and references that used to refer to an object once its storage is reused -- I will refer collectively to those as 3.8/5-7.
In this instance &c is (implicitly) converted to void*, which is one of the correct use of a pointer to storage that has not been yet reused. Similarly p is obtained from &c before the new int is constructed. Its definition could perhaps be moved to after the destruction of c, depending on how deep the implementation magic is, but certainly not after the int construction: paragraph 7 would apply and this is not one of the allowed situations. The construction of the short object also relies on p becoming a pointer to storage.
Now, because int and short are trivial types, I don't have to use the explicit calls to destructors. I don't need the explicit calls to the constructors, either (that is to say, the calls to the usual, Standard placement new declared in <new>). From 3.8 Object lifetime [basic.life]:
1 [...] The lifetime of an object of type T begins when:
storage with the proper alignment and size for type T is obtained, and
if the object has non-trivial initialization, its initialization is complete.
The lifetime of an object of type T ends when:
if T is a class type with a non-trivial destructor (12.4), the destructor call starts, or
the storage which the object occupies is reused or released.
This means that I can rewrite the code such that, after folding the intermediate variable q, I end up with the original snippet.
Do note that p cannot be folded away. That is to say, the following is defintively incorrect:
alignas(alignment) char c[sizeof(int)];
*magic_cast<int*>(&c) = 42;
*magic_cast<short*>(&c) = 42;
If we assume that an int object is (trivially) constructed with the second line, then that must mean &c becomes a pointer to storage that has been reused. Thus the third line is incorrect -- although due to 3.8/5-7 and not due to aliasing rules strictly speaking.
If we don't assume that, then the second line is a violation of aliasing rules: we're reading what is actually a char c[sizeof(int)] object through a glvalue of type int, which is not one of the allowed exception. By comparison, *magic_cast<unsigned char>(&c) = 42; would be fine (we would assume a short object is trivially constructed on the third line).
Just like Alf, I would also recommend that you explicitly make use of the Standard placement new when using storage. Skipping destruction for trivial types is fine, but when encountering *some_magic_pointer = foo; you're very much likely facing either a violation of 3.8/5-7 (no matter how magically that pointer was obtained) or of the aliasing rules. This means storing the result of the new expression, too, since you most likely can't reuse the magic pointer once your object is constructed -- due to 3.8/5-7 again.
Reading the bytes of an object (this means using char or unsigned char) is fine however, and you don't even to use reinterpret_cast or anything magic at all. static_cast via cv void* is arguably fine for the job (although I do feel like the Standard could use some better wording there).
This too:
// valid: char -> type
alignas(int) char c[sizeof(int)];
int * i = reinterpret_cast<int*>(c);
That is not correct. The aliasing rules state under which circumstances it is legal/illegal to access an object through an lvalue of a different type. There is an specific rule that says that you can access any object through a pointer of type char or unsigned char, so the first case is correct. That is, A => B does not necessarily mean B => A. You can access an int through a pointer to char, but you cannot access a char through a pointer to int.
For the benefit of Alf:
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,
a cv-qualified version of the dynamic type of the object,
a type similar (as defined in 4.4) to the dynamic type of the object,
a type that is the signed or unsigned type corresponding to the dynamic type of the object,
a type that is the signed or unsigned type corresponding to a cv-qualified version of the dynamic type of the object,
an aggregate or union type that includes one of the aforementioned types among its elements or non- static data members (including, recursively, an element or non-static data member of a subaggregate or contained union),
a type that is a (possibly cv-qualified) base class type of the dynamic type of the object,
a char or unsigned char type.
Regarding the validity of …
alignas(int) char c[sizeof(int)];
int * i = reinterpret_cast<int*>(c);
The reinterpret_cast itself is OK or not, in the sense of producing a useful pointer value, depending on the compiler. And in this example the result isn't used, in particular, the character array isn't accessed. So there is not much more that can be said about the example as-is: it just depends.
But let's consider an extended version that does touch on the aliasing rules:
void foo( char* );
alignas(int) char c[sizeof( int )];
foo( c );
int* p = reinterpret_cast<int*>( c );
cout << *p << endl;
And let's only consider the case where the compiler guarantees a useful pointer value, one that would place the pointee in the same bytes of memory (the reason that this depends on the compiler is that the standard, in §5.2.10/7, only guarantees it for pointer conversions where the types are alignment-compatible, and otherwise leave it as "unspecified" (but then, the whole of §5.2.10 is somewhat inconsistent with §9.2/18).
Now, one interpretation of the standard's §3.10/10, the so called "strict aliasing" clause (but note that the standard does not ever use the term "strict aliasing"),
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,
a cv-qualified version of the dynamic type of the object,
a type similar (as defined in 4.4) to the dynamic type of the object,
a type that is the signed or unsigned type corresponding to the dynamic type of the object,
a type that is the signed or unsigned type corresponding to a cv-qualified version of the dynamic type of the object,
an aggregate or union type that includes one of the aforementioned types among its elements or non- static data members (including, recursively, an element or non-static data member of a subaggregate or contained union),
a type that is a (possibly cv-qualified) base class type of the dynamic type of the object,
a char or unsigned char type.
is that, as it itself says, concerns the dynamic type of the object residing in the c bytes.
With that interpretation, the read operation on *p is OK if foo has placed an int object there, and otherwise not. So in this case, a char array is accessed via an int* pointer. And nobody is in any doubt that the other way is valid: even though foo may have placed an int object in those bytes, you can freely access that object as a sequence of char values, by the last dash of §3.10/10.
So with this (usual) interpretation, after foo has placed an int there, we can access it as char objects, so at least one char object exists within the memory region named c; and we can access it as int, so at least that one int exists there also; and so David’s assertion in another answer that char objects cannot be accessed as int, is incompatible with this usual interpretation.
David's assertion is also incompatible with the most common use of placement new.
Regarding what other possible interpretations there are, that perhaps could be compatible with David's assertion, well, I can't think of any that make sense.
So in conclusion, as far as the Holy Standard is concerned, merely casting oneself a T* pointer to the array is practically useful or not depending on the compiler, and accessing the pointed to could-be-value is valid or not depending on what's present. In particular, think of a trap representation of int: you would not want that blowing up on you, if the bitpattern happened to be that. So to be safe you have to know what's in there, the bits, and as the call to foo above illustrates the compiler can in general not know that, like, the g++ compiler's strict alignment-based optimizer can in general not know that…