2021 年 3 月 20 日更新:
最近在 Reddit 上提出了同样的问题,有人指出我原来的答案是有缺陷的,因为它没有考虑到这个别名规则:
如果程序尝试通过类型与以下类型之一不相似的泛左值访问对象的存储值,则行为未定义:
- 对象的动态类型,
- 对应于对象动态类型的有符号或无符号类型,或
- char、unsigned char 或 std::byte 类型。
根据相似性规则,对于上述任何情况,这两种数组类型都不相似,因此通过 3D 数组访问 1D 数组在技术上是未定义的行为。(这绝对是其中一种情况,在实践中,它几乎可以肯定适用于大多数编译器/目标)
请注意,原始答案中的引用是指较旧的 C++11 草案标准
原答案:
reinterpret_cast
参考文献
该标准规定,类型的左值T1
可以是reinterpret_cast
指向的引用,T2
如果指向的指针T1
可以reinterpret_cast
指向指向的指针T2
(第 5.2.10/11 节):
如果可以使用 reinterpret_cast 将类型“pointer to”的表达式显式转换为“pointer to ”类型,则可以将类型的左值表达式强制T1
转换为“reference to ”类型。T2
T1
T2
所以我们需要确定是否int(*)[N]
可以将 a 转换为int(*)[I][J][K]
。
reinterpret_cast
指针
一个指针T1
可以reinterpret_cast
指向一个指针,T2
如果两者T1
都是T2
标准布局类型并且T2
没有比T1
(§5.2.10/7)更严格的对齐要求:
当“指向 T1 的指针”类型的纯右值 v 转换为“指向 cv T2 的指针”类型时,结果是static_cast<cv T2*>(static_cast<cv void*>(v))
如果T1
和T2
都是标准布局类型(3.9)并且 的对齐要求T2
不比 的更严格T1
,或者如果任何一种类型都是无效的。
是int[N]
和int[I][J][K]
标准布局类型吗?
int
是标量类型,标量类型的数组被认为是标准布局类型(第 3.9/9 节)。
标量类型、标准布局类类型(第 9 条)、此类类型的数组以及这些类型的 cv 限定版本(3.9.3)统称为标准布局类型。
没有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);
where*
和&
是内置运算符,则相当于:
int (&arr3d)[I][J][K] = *static_cast<int (*)[I][J][K]>(static_cast<void*>(&arr1d));
static_cast
通过void*
标准转换(§4.10/2)允许to static_cast
:void*
“指向 cv 的指针T
”类型的纯右值,其中T
是对象类型,可以转换为“指向 cv void 的指针”类型的纯右值。将“指向 cv 的指针T
”转换为“指向 cv void 的指针”的结果指向类型对象所在的存储位置的开始T
,就好像该对象是类型最派生的对象 (1.8) T
(即,而不是基类子对象)。
然后允许static_cast
to int(*)[I][J][K]
(§5.2.9/13):
“指向 cv1 的指针void
”类型的纯右值可以转换为“指向 cv2 的指针”类型的纯右值T
,其中T
是对象类型,且 cv2 与 cv1 具有相同的 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 节):
如果程序尝试通过非下列类型之一的泛左值访问对象的存储值,则行为未定义:
所以除非我错过了什么。我会说这个程序定义明确。