Welcome to ShenZhenJia Knowledge Sharing Community for programmer and developer-Open, Learning and Share
menu search
person
Welcome To Ask or Share your Answers For Others

Categories

Let's say we have a 2D int array:

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 ints? 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

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.

Can I absolutely treat 2D arrays as 1D arrays when needed?

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
thumb_up_alt 0 like thumb_down_alt 0 dislike
287 views
Welcome To Ask or Share your Answers For Others

1 Answer

Thank you all for replying and commenting, but I think the correct answer is - as it stands the code exhibits technical UB, though correctable. I have looked through some of those questions [1, 2] @xskxzr linked and it led me to this quote from the standard:

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. [?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:

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:

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]. So basically the use of reinterpret_cast the way I used it leads to 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?:

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 Ts which has Y elements. p + X*Y therefore will be pointing out of range of this 1st array of Ts, hence UB (thanks again to @xskxzr for the link and comment).

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; 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. 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.

TL;DR version

Behaviour in the OP is Undefined because of improper use of reinterpret_cast. 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. The result is an lvalue or xvalue referring to the same object as the original lvalue, but with a different type. 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 and DynamicType are similar.

Type similarity:

Informally, two types are similar if, ignoring top-level 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

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; if the type of the identifier of D contains the auto type-specifier, the program is ill-formed. T is called the array element type;


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
thumb_up_alt 0 like thumb_down_alt 0 dislike
Welcome to ShenZhenJia Knowledge Sharing Community for programmer and developer-Open, Learning and Share

548k questions

547k answers

4 comments

86.3k users

...