更新时间:2021-03-20:
最近在Reddit上提出了同样的问题,并指出我的原始答案存在缺陷,因为它没有考虑到这个别名规则:
如果程序尝试通过类型不类似于以下类型之一的glvalue访问对象的存储值,则行为未定义:
- 对象的动态类型,
- 与对象的动态类型相对应的有符号或无符号类型,或者
- char、unsigned char或std::byte类型。
根据相似性规则,这两种数组类型不类似于上述任何一种情况,因此通过3D数组访问1D数组在技术上是未定义的行为。(这绝对是那些实际上几乎肯定可以在大多数编译器/目标上工作的情况之一)
请注意,原始答案中的引用是指较旧的C++11草案标准
原始答案:
reinterpret_cast
引用
标准规定,如果指向T1
的指针可以通过reinterpret_cast
转换为指向T2
的指针,则类型为T1
的lvalue可以reinterpret_cast
为类型为T2
的引用(§5.2.10/11):
如果类型为T1
的lvalue表达式可以强制转换为类型“T1
指针”的表达式,则可以将其转换为类型“T2
的引用”,使用reinterpret_cast
。
因此,我们需要确定int(*)[N]
是否可以转换为int(*)[I][J][K]
。
reinterpret_cast
指针
如果T1
和T2
都是标准布局类型,并且T2
没有比T1
更严格的对齐要求,则可以将指向T1
的指针reinterpret_cast
为指向T2
的指针(§5.2.10/7):
当将类型为“指向T1”的prvalue v转换为类型“指向cv T2”的指针时,如果T1和T2都是标准布局类型(3.9),并且T2的对齐要求不比T1更严格,或者任一类型为void,则结果为
static_cast<cv T2*>(static_cast<cv void*>(v))
。
1.
int[N]
和
int[I][J][K]
是标准布局类型吗?
-
int
是标量类型,标量类型的数组被视为标准布局类型(§3.9/9)。
- 标量类型、标准布局类类型(第9条)、这些类型的数组和这些类型的cv限定版本(3.9.3)合称为标准布局类型。
2.
int[I][J][K]
是否没有比
int[N]
更严格的对齐要求?
-
alignof
运算符的结果给出完整对象类型的对齐要求(§3.11/2)。
- 由于这两个数组不是任何其他对象的子对象,它们是完整的对象。将
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*
标准转换(§4.10/2)允许将static_cast
转换为void*
:
类型为“指向 cv T
的指针”的 prvalue,其中 T
是对象类型,可以转换为类型为“指向 cv void”的 prvalue。将“指向 cv T
”转换为“指向 cv void”的结果指向存储位置的起始位置,该存储位置是类型为 T
的对象所在的位置,就像该对象是类型为 T
的最派生对象(1.8),而不是基类子对象。
然后允许将static_cast
转换为int(*)[I][J][K]
(§5.2.9/13):
类型为“指向 cv1 void
的指针”的 prvalue 可以转换为类型为“指向 cv2 T
”的 prvalue,其中 T
是对象类型,cv2 是与 cv1 相同或更大的 cv-qualification。
因此,强制转换是正确的! 但是我们能通过新数组引用访问对象吗?
访问数组元素
对于像arr3d[E2]
这样的数组执行下标操作相当于*((E1)+(E2))
(§5.2.1/1)。让我们考虑以下数组下标操作:
arr3d[3][2][1]
首先,
arr3d[3]
等价于
*((arr3d)+(3))
。lvalue
arr3d
经历了数组到指针的转换,得到了一个
int(*)[2][1]
。没有要求底层数组必须是正确类型进行此转换。然后访问了指针的值(根据§3.10合法),并且将值3加到它上面。这种指针算术也是合法的(根据§5.7/5):
如果指针操作数和结果都指向同一数组对象或该数组对象之后的最后一个元素,则评估不应产生溢出;否则,行为未定义。
然后解除引用此指针以给出一个
int[2][1]
。对于下一个两个下标,进行相同的过程,导致最终适当数组索引处的
int
lvalue。由于
*
的结果(根据§5.3.1/1),它是一个lvalue:
一元*运算符执行间接引用:应用于它的表达式应该是一个对象类型的指针或函数类型的指针,结果是一个lvalue,它引用表达式指向的对象或函数。
然后,通过此lvalue访问实际的
int
对象是完全可以的,因为lvalue也是
int
类型(根据§3.10/10):
如果程序尝试通过除以下类型之一的glvalue访问对象的存储值,则行为未定义:
对象的动态类型
[...]
所以,除非我漏掉了什么,否则我会说此程序已经被定义良好。