将一维数组重塑为多维数组

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

Taking into consideration the entire C++11 standard, is it possible for any conforming implementation to succeed the first assertion below but fail the latter?

#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?
}

如果不是,这在技术上是否是 UB,如果删除第一个断言,答案是否会改变(reinterpret_cast 是否保证在此处保留地址?)?另外,如果在相反的方向(3d 到 1d)或从 6x35 阵列到 10x21 阵列进行整形怎么办?

If not, is this technically UB or not, and does that answer change if the first assertion is removed (is reinterpret_cast guaranteed to preserve addresses here?)? Also, what if the reshaping is done in the opposite direction (3d to 1d) or from a 6x35 array to a 10x21 array?

如果答案是由于 reinterpret_cast 而这是 UB,是否还有其他一些严格合规的重塑方式(例如,通过 static_cast 到/从中间 void *)?

If the answer is that this is UB because of the reinterpret_cast, is there some other strictly compliant way of reshaping (e.g., via static_cast to/from an intermediate void *)?

推荐答案

2021-03-20 更新:

最近在 Reddit 上问过这个问题有人指出我的原始答案有缺陷,因为它没有考虑到这个 别名规则:

This same question was asked on Reddit recently and it was pointed out that my original answer is flawed because it does not take into account this aliasing rule:

如果程序尝试通过类型与以下类型之一不相似的泛左值访问对象的存储值,则行为未定义:

If a program attempts to access the stored value of an object through a glvalue whose type is not similar to one of the following types the behavior is undefined:

  • 对象的动态类型,
  • 与对象的动态类型相对应的有符号或无符号类型,或
  • char、unsigned char 或 std :: byte 类型.

在相似性的规则下,这两种数组类型对于上述任何一种情况都不相似,因此通过 3D 数组访问 1D 数组在技术上是未定义的行为.(这绝对是在实践中几乎可以肯定适用于大多数编译器/目标的情况之一)

Under the rules for similarity, these two array types are not similar for any of the above cases and therefore it is technically undefined behaviour to access the 1D array through the 3D array. (This is definitely one of those situations where, in practice, it will almost certainly work with most compilers/targets)

请注意,原始答案中的参考资料是指较旧的 C++11 草案标准

标准规定,如果指向 T1 可以是 reinterpret_cast 指向 T2 的指针(第 5.2.10/11 节):

The standard states that an lvalue of type T1 can be reinterpret_cast to a reference to T2 if a pointer to T1 can be reinterpret_cast to a pointer to T2 (§5.2.10/11):

T1 类型的左值表达式可以转换为对 T2 的引用"类型的表达式,如果类型为指向 T1 的指针""可以使用 reinterpret_cast 显式转换为指向 T2 的指针"类型.

An lvalue expression of type T1 can be cast to the type "reference to T2" if an expression of type "pointer to T1" can be explicitly converted to the type "pointer to T2" using a reinterpret_cast.

所以我们需要判断一个int(*)[N]是否可以转换成一个int(*)[I][J][K].

So we need to determine if a int(*)[N] can be converted to an int(*)[I][J][K].

指向T1的指针可以reinterpret_cast指向T2的指针,如果T1T2 是标准布局类型,T2 没有比 T1 更严格的对齐要求(第 5.2.10/7 节):

A pointer to T1 can be reinterpret_cast to a pointer to T2 if both T1 and T2 are standard-layout types and T2 has no stricter alignment requirements than T1 (§5.2.10/7):

当类型为指向T1的指针"的纯右值v转换为类型指向cv T2的指针"时,结果为static_cast(static_cast(v)) 如果 T1T2 都是标准布局类型 (3.9) 并且 T2 的对齐要求不比那些更严格T1,或者如果任一类型为 void.

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.

  1. int[N]int[I][J][K] 是标准布局类型吗?

  1. Are int[N] and int[I][J][K] standard-layout types?

int 是标量类型,标量类型的数组被认为是标准布局类型(第 3.9/9 节).

int is a scalar type and arrays of scalar types are considered to be standard-layout types (§3.9/9).

标量类型、标准布局类类型(第 9 条)、此类类型的数组和这些类型的 cv 限定版本 (3.9.3) 统称为标准布局类型.>

Scalar types, standard-layout class types (Clause 9), arrays of such types and cv-qualified versions of these types (3.9.3) are collectively called standard-layout types.

  • int[I][J][K] 是否没有比 int[N] 更严格的对齐要求.

  • Does int[I][J][K] have no stricter alignment requirements than int[N].

    alignof 运算符的结果给出了完整对象类型的对齐要求(第 3.11/2 节).

    The result of the alignof operator gives the alignment requirement of a complete object type (§3.11/2).

    alignof 运算符的结果反映了完整对象情况下类型的对齐要求.

    The result of the alignof operator reflects the alignment requirement of the type in the complete-object case.

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

    Since the two arrays here are not subobjects of any other object, they are complete objects. Applying alignof to an array gives the alignment requirement of the element type (§5.3.6/3):

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

    When alignof is applied to an array type, the result shall be the alignment of the element type.

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

    So both array types have the same alignment requirement.

    这使得 reinterpret_cast 有效并等效于:

    That makes the reinterpret_cast valid and equivalent to:

    int (&arr3d)[I][J][K] = *reinterpret_cast<int (*)[I][J][K]>(&arr1d);
    

    其中 *& 是内置运算符,则相当于:

    where * and & are the built-in operators, which is then equivalent to:

    int (&arr3d)[I][J][K] = *static_cast<int (*)[I][J][K]>(static_cast<void*>(&arr1d));
    

    static_castvoid*

    static_castvoid* 是标准转换允许的(第 4.10/2 节):

    static_cast through void*

    The static_cast to void* is allowed by the standard conversions (§4.10/2):

    类型为指向 cv T 的指针"的纯右值,其中 T 是对象类型,可以转换为类型为指向 cv void 的指针"的纯右值.将指向 cv T 的指针"转换为指向 cv void 的指针"的结果指向 T 类型的对象所在的存储位置的开始,如如果对象是 T 类型的最派生对象 (1.8)(即,不是基类子对象).

    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 "pointer to cv T" to a "pointer to cv void" points to the start of the storage location where the object of type T resides, as if the object is a most derived object (1.8) of type T (that is, not a base class subobject).

    然后允许将 static_cast 转换为 int(*)[I][J][K] (§5.2.9/13):

    The static_cast to int(*)[I][J][K] is then allowed (§5.2.9/13):

    指向 cv1 void 的指针"类型的纯右值可以转换为指向 cv2 T 的指针"类型的纯右值,其中 T> 是一个对象类型,而 cv2 是与 cv1 相同的 cv 限定,或者比 cv1 更高的 cv 限定.

    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.

    所以转换没问题!但是我们可以通过新的数组引用访问对象吗?

    So the cast is fine! But are we okay to access objects through the new array reference?

    对像 arr3d[E2] 这样的数组执行数组下标等价于 *((E1)+(E2)) (§5.2.1/1).让我们考虑以下数组下标:

    Performing array subscripting on an array like arr3d[E2] is equivalent to *((E1)+(E2)) (§5.2.1/1). Let's consider the following array subscripting:

    arr3d[3][2][1]
    

    首先,arr3d[3]等价于*((arr3d)+(3)).左值 arr3d 经过数组到指针的转换,得到一个 int(*)[2][1].不要求底层数组必须是正确的类型才能进行此转换.然后访问指针值(第 3.10 节很好),然后将值 3 添加到其中.这个指针算法也很好(§5.7/5):

    Firstly, arr3d[3] is equivalent to *((arr3d)+(3)). The lvalue arr3d undergoes array-to-pointer conversion to give a int(*)[2][1]. There is no requirement that the underlying array must be of the correct type to do this conversion. The pointers value is then accessed (which is fine by §3.10) and then the value 3 is added to it. This pointer arithmetic is also fine (§5.7/5):

    如果指针操作数和结果都指向同一个数组对象的元素,或者超过数组对象最后一个元素,则求值不会产生溢出;否则,行为未定义.

    If both the pointer operand and the result point to elements of the same array object, or one past the last element of the array object, the evaluation shall not produce an overflow; otherwise, the behavior is undefined.

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

    This this pointer is dereferenced to give an int[2][1]. This undergoes the same process for the next two subscripts, resulting in the final int lvalue at the appropriate array index. It is an lvalue due to the result of * (§5.3.1/1):

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

    The unary * operator performs indirection: the expression to which it is applied shall be a pointer to an object type, or a pointer to a function type and the result is an lvalue referring to the object or function to which the expression points.

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

    It is then perfectly fine to access the actual int object through this lvalue because the lvalue is of type int too (§3.10/10):

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

    If a program attempts to access the stored value of an object through a glvalue of other than one of the following types the behavior is undefined:

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

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

    So unless I've missed something. I'd say this program is well-defined.

  • 相关文章