简体   繁体   English

将二维数组视为一维数组

[英]Treating 2D array as 1D array

Let's say we have a 2D int array:假设我们有一个 2D int数组:

int a[3][4] = { { 1,3,2,4 }, { 2,1,5,3 }, { 0,8,2,3 } };

Is it legal and valid to take its address and reinterpret it as a pointer to 1D array of int s?取其地址并将其重新解释为指向int的一维数组的指针是否合法有效? Basically:基本上:

int *p = reinterpret_cast<int *>(&a);

So that I can do things like (roughly):这样我就可以做(大致)这样的事情:

template<typename T, size_t X, size_t Y>
void sort2(T(&arr)[X][Y])
{
    T *p = reinterpret_cast<T *>(&arr);
    std::sort(p, p + X*Y);
}

DEMO: https://ideone.com/tlm190演示: https : //ideone.com/tlm190

To my knowledge, the standard guarantees that alignment of 2D array would be contiguous in memory, and although p + X*Y technically is out of range is never accessed so should not lead to Undefined Behaviour either.据我所知,该标准保证二维数组的对齐在内存中是连续的,虽然p + X*Y技术上讲是超出范围的,但永远不会被访问,因此也不应该导致未定义的行为。

Can I absolutely treat 2D arrays as 1D arrays when needed?我可以在需要时绝对将二维数组视为一维数组吗?

Thank you all for replying and commenting, but I think the correct answer is - as it stands the code exhibits technical UB, though correctable.感谢大家的回复和评论,但我认为正确的答案是 - 就目前而言,代码展示了技术 UB,但可以更正。 I have looked through some of those questions [ 1 , 2 ] @xskxzr linked and it led me to this quote from the standard :我浏览了其中的一些问题 [ 1 , 2 ] @xskxzr 链接,这让我从标准中找到这句话

If two objects are pointer-interconvertible, then they have the same address, and it is possible to obtain a pointer to one from a pointer to the other via a reinterpret_­cast .如果两个对象是指针可互转换的,则它们具有相同的地址,并且可以通过reinterpret_cast从指向另一个的指针获取指向一个的指针 [ Note : An array object and its first element are not pointer-interconvertible, even though they have the same address . [注意一个数组对象和它的第一个元素是指针不可转换的,即使它们有相同的地址 end note ] 尾注]

Then on reinterpret_cast page there is the following note with an example:然后在reinterpret_cast页面上有以下注释和示例:

Assuming that alignment requirements are met, a reinterpret_cast does not change the value of a pointer outside of a few limited cases dealing with pointer-interconvertible objects:假设满足对齐要求, reinterpret_cast不会在处理指针可互转换对象的一些有限情况之外更改指针的值:

int arr[2];
int* p5 = reinterpret_cast<int*>(&arr); // value of p5 is unchanged by reinterpret_cast and
                                        // is "pointer to arr"

Even though this compiles without warning and runs , this is technically a UB because p5 is technically still a pointer to arr and not to arr[0] .即使这在没有警告的情况下编译并运行,这在技术上是一个 UB,因为从技术上讲p5仍然是指向arr而不是arr[0]的指针。 So basically the use of reinterpret_cast the way I used it leads to UB.所以基本上我使用reinterpret_cast的方式会导致 UB。 Taking the above into account, if I were to create int * directly to the 1st int (and this is ok according to the answer by @codekaizer), then this should be valid, right?:考虑到上述情况,如果我要直接将int *创建到第一个int (根据@codekaizer 的回答这是可以的),那么这应该是有效的,对吧?:

template<typename T, size_t X, size_t Y>
void sort2(T(&arr)[X][Y])
{
    T *p = &arr[0][0]; // or T *p = arr[0];
    std::sort(p, p + X * Y);
}

But it probably is UB as well since the pointer p is pointing to the first T of the first array of T s which has Y elements.但它也可能是 UB,因为指针p指向具有Y元素的T s 的第一个数组的第一个T p + X*Y therefore will be pointing out of range of this 1st array of T s, hence UB (thanks again to @xskxzr for the link and comment).因此p + X*Y将指向第一个T数组的范围,因此是 UB (再次感谢@xskxzr 提供链接和评论)。

If the expression P points to element x[i] of an array object x with n elements, the expressions P + J and J + P (where J has the value j) point to the (possibly-hypothetical) element x[i+j] if 0≤i+j≤n;如果表达式 P 指向具有 n 个元素的数组对象 x 的元素 x[i],则表达式 P + J 和 J + P(其中 J 的值为 j)指向(可能是假设的)元素 x[i+ j] 如果 0≤i+j≤n; otherwise, the behavior is undefined.否则,行为未定义。

So here is my final attempt before I give up:所以这是我放弃之前的最后一次尝试:

template<typename T, size_t X, size_t Y>
void sort2(T(&arr)[X][Y])
{
    T(&a)[X * Y] = reinterpret_cast<T(&)[X * Y]>(arr);
    std::sort(a, a + X * Y);
}

Here T arr[X][Y] is first converted to T a[X*Y] with, again, reinterpret_cast , which I think is now valid.这里T arr[X][Y]首先转换为T a[X*Y] ,再次使用reinterpret_cast ,我认为现在是有效的。 The reinterpreted array a happily decays to a pointer to 1st element of array a[X*Y] ( a + X * Y is also within the range) and gets converted to an iterator in std::sort .重新解释的数组a愉快地衰减到指向数组a[X*Y]第一个元素的指针( a + X * Y也在范围内)并在std::sort转换为迭代器。

TL;DR version TL;DR 版本

Behaviour in the OP is Undefined because of improper use of reinterpret_cast .由于reinterpret_cast使用不当,OP 中的行为未定义。 The correct way to convert 2D array to 1D array would be:将二维数组转换为一维数组的正确方法是:

//-- T arr2d[X][Y]
T(&arr1d)[X*Y] = reinterpret_cast<T(&)[X*Y]>(arr2d);

An lvalue expression of type T1 can be converted to reference to another type T2. T1 类型的左值表达式可以转换为对另一个类型 T2 的引用。 The result is an lvalue or xvalue referring to the same object as the original lvalue, but with a different type.结果是一个左值或 xvalue 引用与原始左值相同的对象,但具有不同的类型。 No temporary is created, no copy is made, no constructors or conversion functions are called.没有临时创建,没有复制,没有构造函数或转换函数被调用。 The resulting reference can only be accessed safely if allowed by the type aliasing rules只有在类型别名规则允许的情况下才能安全地访问结果引用

Aliasing rules : 别名规则

Whenever an attempt is made to read or modify the stored value of an object of type DynamicType through a glvalue of type AliasedType, the behavior is undefined unless one of the following is true:每当尝试通过 AliasedType 类型的泛左值读取或修改 DynamicType 类型的对象的存储值时,除非满足以下任一条件,否则行为未定义:

  • AliasedType and DynamicType are similar. AliasedType 和 DynamicType 类似。

Type similarity : 类型相似度

Informally, two types are similar if, ignoring top-level cv-qualification非正式地,两种类型是相似的 if,忽略顶级 cv-qualification

  • they are both arrays of the same size or both arrays of unknown bound, and the array element types are similar.它们都是相同大小的数组或都是未知边界的数组,数组元素类型相似。

Array element type :数组元素类型

In a declaration T D where D has the form在声明T D ,其中D的形式为

D1 [ constant-expression opt ] attribute-specifier-seq opt

and the type of the identifier in the declaration T D1 is “ derived-declarator-type-list T ”, then the type of the identifier of D is an array type;并且声明T D1中的标识符类型为“ derived-declarator-type-list T ”,则D的标识符类型为数组类型; if the type of the identifier of D contains the auto type-specifier , the program is ill-formed.如果D的标识符类型包含 auto类型说明符,则程序格式错误。 T is called the array element type ; T称为数组元素类型

From http://www.cplusplus.com/doc/tutorial/arrays/来自http://www.cplusplus.com/doc/tutorial/arrays/

int jimmy [3][5];   // is equivalent to
int jimmy [15];     // (3 * 5 = 15)  

when creating an array ( of any dimension ) the array memory is a fixed block of memory in size = sizeof(type) * dim0 * dim1 * ....;创建数组(任何维度)时,数组内存是size = sizeof(type) * dim0 * dim1 * ....;固定的内存块size = sizeof(type) * dim0 * dim1 * ....;

So to your question, yes you can recast the array safely into one dimensional array.所以对于你的问题,是的,你可以安全地将数组重铸为一维数组。

Yes.是的。 It is legal and valid.它是合法有效的。

As per dcl.array :根据dcl.array

If E is an n-dimensional array of rank i×j×⋯×k , then E appearing in an expression that is subject to the array-to-pointer conversion is converted to a pointer to an (n−1)-dimensional array with rank j×⋯×k.如果 E 是秩为 i×j×⋯×k 的 n 维数组,则出现在需要进行数组到指针转换的表达式中的 E 将转换为指向 (n−1) 维数组的指针秩为 j×⋯×k。 If the * operator, either explicitly or implicitly as a result of subscripting, is applied to this pointer, the result is the pointed-to (n−1)-dimensional array, which itself is immediately converted into a pointer.如果*运算符(作为下标的结果显式或隐式)应用于此指针,则结果是指向的 (n-1) 维数组,该数组本身立即转换为指针。

A 2D array cannot be treated as a 1D array二维数组不能被视为一维数组

As noted by @KillzoneKid, the address of an array is not pointer interconvertable with that of the first element even though they share the same address value.正如@KillzoneKid 所指出的,数组的地址不能与第一个元素的地址相互转换,即使它们共享相同的地址值。

constexpr provides a convenient way to evaluate UB. constexpr提供了一种评估 UB 的便捷方法。 Compilers are required to detect UB when constexpr s are evaluated.当评估constexpr时,编译器需要检测 UB。 Thus a simple test can be made.这样就可以进行简单的测试。 This function, when evaluated during compile time will detect accessing values beyond an array's range.此函数在编译时评估时将检测超出数组范围的访问值。

// sum "n" sequential ints
constexpr int sum(const int* pi, int n) {
    int sum = 0;
    while (n-- > 0)
        sum += *pi++;
    return sum;
};

When this is called at runtime with a pointer to the first element of a 1D or 2D array it will sum the elements but is UB if "n" goes beyond the boundary of the array.当在运行时使用指向一维或二维数组的第一个元素的指针调用它时,它将对元素求和,但如果“n”超出数组的边界,则为 UB。 For a 2D array, this would be the extent of the lowest dimension.对于二维数组,这将是最低维度的范围。 Not the size of the whole array.不是整个数组的大小。

Examining different instances of a pointer to an int we can see UB occurs once we try to access a value beyond the array dimension.检查指向 int 的指针的不同实例,我们可以看到,一旦我们尝试访问超出数组维度的值,就会发生 UB。

int main()
{
    constexpr int i0{1};
    constexpr int i1[2]{ 1,2 };
    constexpr int i2[2][2] = { { 1,2}, {3,4} };

    constexpr int sum0 = sum(&i0, 1);       // fails for n>1
    constexpr int sum1 = sum(&i1[0], 2);    // fails for n>2
    constexpr int sum2 = sum(&i2[0][0], 2); // fails for n>2
    const int sum3 = sum(&i2[0][0], 4);     // runtime calc, UB. fails if constexpr

    return sum0 + sum1 + sum2 + sum3;       // 17
}

For accesses beyond existing data, such as in sum0 and sum1, UB is clear.对于超出现有数据的访问,例如在 sum0 和 sum1 中,UB 是明确的。 But sum2 points to existing data for n=[0:3) yet constexpr evaluation shows it to be UB for n=4.但是 sum2 指向 n=[0:3) 的现有数据,但 constexpr 评估表明它是 n=4 的 UB。

I was somewhat surprised to learn this.得知这一点,我有些惊讶。 Every compiler I've ever used worked as expected when doing things like scaling all the coefficients of a matrix by a fixed amount.我使用过的每个编译器在执行诸如按固定量缩放矩阵的所有系数之类的操作时都按预期工作。 But I can see rationales based on optimization assumptions that portions of a matrix won't change from the result of a function call on another part not in the same array sequence.但是我可以看到基于优化假设的基本原理,即矩阵的部分不会因对不在同一数组序列中的另一部分的函数调用的结果而改变。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM