2021-03-20 更新:
最近同样的问题是asked on Reddit,有人指出我原来的答案是有缺陷的,因为它没有考虑到这个aliasing rule:
如果程序尝试通过类型与以下类型之一不相似的泛左值访问对象的存储值,则行为未定义:
- 对象的动态类型,
- 对应于对象动态类型的有符号或无符号类型,或
- char、unsigned char 或 std::byte 类型。
根据similarity 的规则,对于上述任何情况,这两种数组类型都不相似,因此通过 3D 数组访问 1D 数组在技术上是未定义的行为。 (这绝对是其中一种情况,在实践中,它几乎可以肯定适用于大多数编译器/目标)
请注意,原始答案中的引用是指较旧的 C++11 草案标准
原答案:
reinterpret_cast 的参考文献
标准规定T1 类型的左值可以是reinterpret_cast 对T2 的引用,如果指向T1 的指针可以是reinterpret_cast 指向T2 的指针(§5.2.10 /11):
如果“T1 的指针”类型的表达式可以显式转换为“T2 的指针”类型,则T1 类型的左值表达式可以转换为“T2 的引用”类型使用 reinterpret_cast。
所以我们需要确定int(*)[N]是否可以转换为int(*)[I][J][K]。
reinterpret_cast 的指针
如果T1 和T2 都是标准布局类型并且T2 没有比T1 更严格的对齐要求,则指向T1 的指针可以是指向T2 的指针的reinterpret_cast(§ 5.2.10/7):
当“指向 T1 的指针”类型的纯右值 v 转换为“指向 cv T2 的指针”类型时,如果 T1 和 T2 都是标准布局类型 (3.9) 和T2 的对齐要求并不比 T1 的对齐要求严格,或者如果任一类型为 void。
-
int[N] 和 int[I][J][K] 是标准布局类型吗?
int 是标量类型,标量类型的数组被视为标准布局类型(第 3.9/9 节)。
标量类型、标准布局类类型(第 9 条)、此类类型的数组以及这些类型的 cv 限定版本(3.9.3)统称为标准布局类型。 p>
-
int[I][J][K] 是否没有比int[N] 更严格的对齐要求。
alignof 运算符的结果给出了完整对象类型的对齐要求(第 3.11/2 节)。
alignof 运算符的结果反映了完整对象情况下类型的对齐要求。
由于这里的两个数组不是任何其他对象的子对象,它们是完整的对象。将alignof 应用于数组给出了元素类型的对齐要求(第 5.3.6/3 节):
alignof应用于数组类型时,结果应为元素类型的对齐方式。
所以这两种数组类型具有相同的对齐要求。
这使得reinterpret_cast 有效并等同于:
int (&arr3d)[I][J][K] = *reinterpret_cast<int (*)[I][J][K]>(&arr1d);
其中* 和& 是内置运算符,则相当于:
int (&arr3d)[I][J][K] = *static_cast<int (*)[I][J][K]>(static_cast<void*>(&arr1d));
static_cast 到 void*
标准转换允许 static_cast 到 void* (§4.10/2):
“指向 cv T”类型的纯右值,其中T 是一个对象类型,可以转换为“指向 cv void 的指针”类型的纯右值。将“指向 cv T 的指针”转换为“指向 cv void”的结果指向 T 类型的对象所在的存储位置的开始,就好像该对象是最派生的对象(1.8 ) 类型为T(即不是基类子对象)。
然后允许 static_cast 到 int(*)[I][J][K] (§5.2.9/13):
“指向 cv1 void”类型的纯右值可以转换为“指向 cv2 T”类型的纯右值,其中T 是对象类型,而 cv2 与 cv 限定相同,或比 cv1 更高的 cv 限定。
所以转换没问题!但是我们可以通过新的数组引用访问对象吗?
访问数组元素
对像arr3d[E2] 这样的数组执行数组下标等效于*((E1)+(E2)) (§5.2.1/1)。让我们考虑以下数组下标:
arr3d[3][2][1]
首先,arr3d[3] 等价于*((arr3d)+(3))。左值arr3d 经过数组到指针的转换得到int(*)[2][1]。不要求底层数组必须是正确的类型才能进行此转换。然后访问指针值(第 3.10 节很好),然后将值 3 添加到它。这种指针算法也很好(§5.7/5):
如果指针操作数和结果都指向同一个数组对象的元素,或者超过数组对象的最后一个元素,则计算不应产生溢出;否则,行为未定义。
这个 this 指针被取消引用以提供int[2][1]。这对接下来的两个下标进行相同的过程,从而在适当的数组索引处产生最终的 int 左值。由于* (§5.3.1/1) 的结果,它是一个左值:
一元 * 运算符执行间接:应用它的表达式应该是指向对象类型的指针,或指向函数类型的指针,结果是一个左值,指向表达式指向的对象或函数.
然后通过这个左值访问实际的int 对象是完全可以的,因为左值也是int 类型(第 3.10/10 节):
如果程序尝试通过以下类型之一以外的左值访问对象的存储值,则行为未定义:
所以除非我错过了什么。我会说这个程序定义明确。