In the C norm, the following rule:
C23 6.3.2.3 § 7
A pointer to an object type may be converted to a pointer to a different object type. If the resulting pointer is not correctly aligned for the referenced type, the behavior is undefined.
Thus, in this snippet:
char c ;
int* p = (int*) &c ; // p may be unaligned!
*p = 42; // possible failure, but the UB is on the line above
We might have an undefined behavior on line 2, because we have no guarantee that the alignment is correct.
Now, in the following snippet:
char c;
int *tmp;
*((char **)&tmp) = &c;
int *p = tmp; // p may be unaligned!
*p = 42;
We again have a possibly unaligned pointer. However this time, I do not see a section of the C norm that explains where is the undefined behavior. After reading a few other SO questions, I guess that here the problem is the strict aliasing: the pointer tmp is written as a char* while read as int*. Can someone confirm this?
If so, I guess that for compiler that have the option -fno-strict-aliasing, the location of the undefined behavior when the option is active is not really documented :(
In the second example, (char **)&tmp is a conversion between two non-compatible pointer types. Like in the first example, the compiler/system is free to trap upon misalignment at the point of the pointer conversion.
In both examples, there is also strict aliasing UB which happens at the point of *p = 42;.
To find this in the standard, we may first note the general statement in C23 chapter 4:
If a "shall" or "shall not" requirement that appears outside of a constraint or runtime-constraint is violated, the behavior is undefined.
And then the part about "strict aliasing" in C23 6.5 §7:
An object shall have its stored value accessed only by an lvalue expression that has one of the following types:
— a type compatible with the effective type of the object, ...
*p is a so-called lvalue expression where addressable memory is accessed, but this is done through the wrong, non-compatible type int rather than the so-called effective type which is char. And so a "shall" requirement was violated and this is UB.
Futhermore there's C23 6.5.4.3 about the unary * operator which states:
If an invalid value has been assigned to the pointer, the behavior of the unary
*operator is undefined
Where "invalid" is explained in an informative foot note:
Among the invalid values for dereferencing a pointer by the unary
*operator are a null pointer, an address inappropriately aligned for the type of object pointed to, and the address of an object after the end of its lifetime.