将一维数组重新整形为多维数组

Ste*_*Lin 15 c++ multidimensional-array language-lawyer c++11

考虑到整个C++ 11标准,任何符合要求的实现是否有可能成功完成下面的第一个断言,但后者失败了?

#include <cassert>

int main(int, char**)
{  
    const int I = 5, J = 4, K = 3;
    const int N = I * J * K;

    int arr1d[N] = {0};
    int (&arr3d)[I][J][K] = reinterpret_cast<int (&)[I][J][K]>(arr1d);
    assert(static_cast<void*>(arr1d) ==
           static_cast<void*>(arr3d)); // is this necessary?

    arr3d[3][2][1] = 1;
    assert(arr1d[3 * (J * K) + 2 * K + 1] == 1); // UB?
}
Run Code Online (Sandbox Code Playgroud)

如果不是,这在技术上是不是UB,并且如果第一个断言被删除,那么答案是否会改变(reinterpret_cast保证在这里保留地址?)?另外,如果重塑是在相反方向(3d到1d)或从6x35阵列到10x21阵列完成的,该怎么办?

编辑:如果答案是因为这是UB reinterpret_cast,是否还有一些其他严格遵守的重塑方式(例如,static_cast来自/来自中间人void *)?

Jos*_*eld 23

reinterpret_cast 参考文献

该标准规定,如果指针指向(§5.2.10/ 11)的指针,则类型的左值T1可以是reinterpret_cast对引用的引用:T2T1reinterpret_castT2

如果可以使用reinterpret_cast 将"指向" 的类型的表达式显式转换为"指向" 的类型,则T1可以将类型的左值表达式强制转换为"引用T2"类型.T1T2

所以我们需要确定a是否int(*)[N]可以转换为int(*)[I][J][K].

reinterpret_cast 指针

一个指针,指向T1可以是reinterpret_cast至一个指针T2如果两个T1T2是标准布局类型和T2具有比没有严格对齐要求T1(§5.2.10/ 7):

当型"指针T1"的prvalue v是转换为类型"指向cv T2",其结果是static_cast<cv T2*>(static_cast<cv void*>(v)),如果这两个T1T2是标准布局类型(3.9)和的对准要求T2并不比的更严格的T1,或如果任何一种类型无效.

  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运算符的结果反映了完整对象情况下类型的对齐要求.

    由于这里的两个数组不是任何其他对象的子对象,因此它们是完整的对象.应用于alignof数组会给出元素类型的对齐要求(第5.3.6/3节):

    alignof应用于数组类型时,结果应为元素类型的对齐.

    因此两种数组类型都具有相同的对齐要求.

这使得reinterpret_cast有效且相当于:

int (&arr3d)[I][J][K] = *reinterpret_cast<int (*)[I][J][K]>(&arr1d);
Run Code Online (Sandbox Code Playgroud)

where *&是内置运算符,它们相当于:

int (&arr3d)[I][J][K] = *static_cast<int (*)[I][J][K]>(static_cast<void*>(&arr1d));
Run Code Online (Sandbox Code Playgroud)

static_cast 通过 void*

标准转换允许使用static_castto void*(§4.10/ 2):

类型为"指向cv的指针"的prvalue T,其中T是对象类型,可以转换为类型为"指向cv void的指针"的prvalue.将"指向cv"的指针转换为"指向Tcv void的指针"的结果指向类型对象T所在的存储位置的开始,就好像该对象是类型最派生的对象(1.8)T(即,而不是基类子对象).

然后允许使用static_castto int(*)[I][J][K](§5.2.9/ 13):

类型为"指向cv1的指针void"的prvalue可以转换为类型为"指向cv2的指针"的prvalue T,其中T是对象类型,cv2与cv1具有相同的cv-qualification或更高的cv资格.

演员阵容很好!但我们可以通过新的数组引用访问对象吗?

访问数组元素

在数组上执行数组下标arr3d[E2]等同于*((E1)+(E2))(§5.2.1/ 1).让我们考虑以下数组下标:

arr3d[3][2][1]
Run Code Online (Sandbox Code Playgroud)

首先,arr3d[3]相当于*((arr3d)+(3)).左值arr3d经历数组到指针的转换以给出一个int(*)[2][1].不要求底层数组必须具有正确的类型才能执行此转换.然后访问指针值(§3.10很好),然后将值3添加到它.这个指针算法也很好(§5.7/ 5):

如果指针操作数和结果指向相同的数组对象,或一个过去的数组对象的最后一个元素的元素两者,所述评估也不得产生溢出; 否则,行为未定义.

这个指针被解引用以给出一个int[2][1].对于接下来的两个下标,这经历了相同的过程,从而在int适当的数组索引处产生最终的左值.由于*(§5.3.1/ 1)的结果,这是一个左值:

一元*运算符执行间接:它所应用的表达式应该是指向对象类型的指针,或指向函数类型的指针,结果是指向表达式指向的对象或函数的左值.

然后完全可以int通过这个左值访问实际对象,因为左值也是类型int(§3.10/ 10):

如果程序试图通过以下类型之一以外的glvalue访问对象的存储值,则行为未定义:

  • 对象的动态类型
  • [...]

除非我错过了什么.我会说这个程序定义明确.