UPDATE: The following applies to C++ only, for C scroll down.
In short, there's no UB in C++ and there is UB in C.
8.3.4/7
says:
A consistent rule is followed for multidimensional arrays. If E is an n-dimensional array of rank i x j x ... x k,
then E appearing in an expression that is subject to the array-to-pointer conversion (4.2) is converted to a
pointer to an (n - 1)-dimensional array with rank j x ... x k. If the * operator, either explicitly or implicitly
as a result of subscripting, is applied to this pointer, the result is the pointed-to (n - 1)-dimensional array,
which itself is immediately converted into a pointer.
So this won't produce error in C++ (and will work as expected):
T *p2 = **pa2;
T *p3 = ***pa3;
Regarding whether this is UB or not. Consider the very first conversion:
T(*pa1)[6] = (T(*)[6])a;
In C++ it's in fact
T(*pa1)[6] = reinterpret_cast<T(*)[6]>(a);
And this is what the standard says about reinterpret_cast
:
An object pointer can be explicitly converted to an object pointer of a different type. 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.
So a
is converted to pa1
through static_cast
to void*
and back. Static cast to void*
is guaranteed to return the real address address of a
as stated in 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 non-null pointer value of a pointer to object type to a “pointer to
cv void” represents the address of the same byte in memory as the original pointer value.
Next static cast to T(*)[6]
is again guaranteed to return the same address as stated in 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. The null pointer
value is converted to the null pointer value of the destination type. If the original pointer value represents
the address A of a byte in memory and A satisfies the alignment requirement of T, then the resulting pointer
value represents the same address as the original pointer value, that is, A
So the pa1
is guaranteed point to the same byte in memory as a
, and so access to data through it is perfectly valid because the alignment of arrays is the same as the alignment of underlying type.
What about C?
Consider again:
T(*pa1)[6] = (T(*)[6])a;
In C11 standard, 6.3.2.3/7
states the following:
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. Otherwise, when converted back again, the result shall compare equal to the
original pointer. When a pointer to an object is converted to a pointer to a character type,
the result points to the lowest addressed byte of the object. Successive increments of the
result, up to the size of the object, yield pointers to the remaining bytes of the object.
It means that unless the conversion is to char*
, the value of converted pointer is not guaranteed to be equal to value of original pointer, resulting in undefined behavior when accessing data through converted pointer. In order to make it work, the conversion has to be done explicitly through void*
:
T(*pa1)[6] = (T(*)[6])(void*)a;
Conversions back to T*
T *p = a;
T *p1 = *pa1;
T *p2 = **pa2;
T *p3 = ***pa3;
All of these are conversions from array of T
to pointer to T
, which are valid in both C++ and C, and no UB is triggered by accessing the data through converted pointers.