1

I've read all the related clauses of the Standard, but I couldn't interpret them correctly for this particular case:

const int arr[4][2] = {};
const int *ptr1 = (const int*)arr; // Is it OK/NOK and why?
const int *ptr2 = arr[0];          // Is this the same?
const int *ptr3 = &arr[0][0];      // Same?

What does the Standard say about this cast?

UPD.

And here another related question:

int i0 = ptr1[0];
int i2 = ptr2[2];
int i7 = ptr3[7];

What does the Standard say about getting values directly from these pointers?

Denis Petrov
  • 851
  • 7
  • 16
  • In C++ `(const int*)arr` is not legal. No clue about C. – NathanOliver Aug 30 '21 at 20:05
  • How about `const int *ptr = &arr[0][0]` (no cast). (there is still concerns about pointer arithmetic). – Jarod42 Aug 30 '21 at 20:16
  • It's not legal in c++ with the notable exception of arrays of `std::complex<>` structured as `double[N][2]` which carves out an exception because of extensive use of external libs. It's a ridiculous restriction. – doug Aug 30 '21 at 20:16
  • @Jarod42 You can only legally access the extent of `arr[0]` with the ptr. That is `arr[0][0]` and `arr[0][1]` with the ptr – doug Aug 30 '21 at 20:17
  • Doug, I've updated the question giving more casts. Could you explain the difference? – Denis Petrov Aug 30 '21 at 20:56
  • ptr2 and ptr3 are both limited to accessing `arr[0][[0]` and `arr[0][1]`, not, for instance `arr[1][0]` via `ptr2[2]`, though I don't know of any compiler that wouldn't do what is expected. A lot of code would break as this is an area that a lot of written code wrongly assumes is legal even if it works. – doug Aug 30 '21 at 21:20
  • @doug: The Standard actually leaves it unclear whether that is or is not allowed... it comes down to whether "an array object" can mean the "multidimensional array" full object or not. – Ben Voigt Aug 30 '21 at 21:36
  • @BenVoigt Well then they need to clarify it. I point to the exception made for `std::complex` specifically to address accessing that would be otherwise be presumably UB. I find it annoying because it comes up from time to time in code I run across and even write and I hate working around it. – doug Aug 30 '21 at 21:59
  • @BenVoigt I just checked it using the constexpr techinque to discover UB see: https://godbolt.org/z/6Y64Wvszq – doug Aug 30 '21 at 22:34
  • Please select one language tag. C and C++ are different languages; dual tagging this question just doubles the number of answers required and goes against the "one question per question" design of the site – M.M Aug 31 '21 at 02:07
  • @doug: Yet [this variation](https://godbolt.org/z/sT8ea3KfY) shows that a pointer to the first element can be used to access them all. Same `constexpr` trick of searching out UB. – Ben Voigt Aug 31 '21 at 15:06
  • @BenVoigt Looks like a GCC compiler bug. `reinterpret_cast` is not allowed evaluating constexpr (core const expr). See http://eel.is/c++draft/expr.const#5.15 However, I view these restrictions (not allowing multi dim array ptrs converted to a linear array ptr with full access) as largely unnecessary and while technically UB, It's pretty commonly done and a lot of code would break if not allowed. – doug Aug 31 '21 at 17:28
  • @doug: It worked with C-style cast as well. And with either cast flavor, it does indeed error when the offset is increased beyond the actual end of the full "multidimensional array" object. – Ben Voigt Aug 31 '21 at 17:31
  • @BenVoigt I would expect so. Still a compiler bug. Other compilers that I've checked do not allow it. Sounds like a nice compiler extension. – doug Aug 31 '21 at 17:33
  • @BenVoigt Wow, GCC is rather tolerant. No way this is not technical UB but GCC likes it https://godbolt.org/z/ncbMb3Wrd Would make my life simpler if the spec allowed this. – doug Aug 31 '21 at 17:53

2 Answers2

5

Is array-of-arrays-of-T to pointer-of-T cast a legal operation in C/C++ according to the Standards?

const int *ptr1 = (const int*)arr; // Is it OK/NOK and why?

Yes it is legal. arr will first decay to a pointer to first element i.e. const int(*)[4] which is an object pointer. const int *ptr is also an object pointer type. All object pointer types can be explicitly converted to an object pointer of a different type in C++.

However, although the cast is legal, it would not be legal to indirect through the pointer and attempt to access the pointed object due to "strict aliasing" rule. It should be OK after laundering though:

auto ptr = std::launder(reinterpret_cast<const int*>(arr));

But better to just use:

auto ptr = &arr[0][0]

Which achieves the same. It won't help with accessing elements outside of the first subarray though.


UPD

const int *ptr2 = arr[0];          // Is this the same?
const int *ptr3 = &arr[0][0];      // Same?

Neither of these are casts, both are well-formed and are effectively equivalent.

And here another related question:

int i0 = ptr[0];
int i2 = ptr[2];
int i7 = ptr[7];

This is undefined behaviour even with the laundering. You can only access arr[0] through the reinterpreted pointer because it is derived from the decayed pointer to first subarray. Accessing ptr[0] and ptr[1] would be fine for your ptr2 and ptr3 and my laundered example.

eerorika
  • 232,697
  • 12
  • 197
  • 326
  • 2
    `std::launder` doesn't fix strict aliasing UB. See bullets 2 and 4 here: https://en.cppreference.com/w/cpp/utility/launder – NathanOliver Aug 30 '21 at 20:51
  • What exact "strict aliasing" tells us about it? – Denis Petrov Aug 30 '21 at 20:58
  • [What is the strict aliasing rule?](https://stackoverflow.com/questions/98650/what-is-the-strict-aliasing-rule) Glad you asked! Sidenote: Ignore the bit about using `union`s. That's pretty much C only. – user4581301 Aug 30 '21 at 21:14
  • @NathanOliver Those bullets are satisfied in the example, so laundering doesn't need to "fix" that. – eerorika Aug 30 '21 at 21:19
  • No, `&arr[0][0]` doesn't give access, without UB, beyond `arr[0][1]`` See this in compiler explorer https://godbolt.org/z/6Y64Wvszq – doug Aug 30 '21 at 22:40
  • @doug Indeed. I didn't say that it does. – eerorika Aug 30 '21 at 22:51
  • Ok, then you might want to edit this "But better to just use: auto ptr = &arr[0][0]" since you added clarification in your UPD – doug Aug 31 '21 at 01:36
  • @doug I added clarification. – eerorika Aug 31 '21 at 01:39
  • 1
    It's not a strict aliasing violation to access an object of type `const int` through an lvalue of type `const int` – M.M Aug 31 '21 at 02:08
0

With raw arrays and structures, this is perfectly well supported, a pointer to an aggregate type is inter-convertible with a pointer to its first subobject (or in the case of unions, any direct subobject). A pointer to an array holds the address of its first element. These rules apply recursively, individually and mutually.

There is no strict-aliasing violation.

It is questionable to use pointer-arithmetic to walk outside of the first "row" into subsequent rows. Comparison between two pointers anywhere in the same full-object is however always permitted.

Where you get into trouble is with C++ smart collections, such as std::vector. There you can still take &v[0][0], but only the elements of the first vector are contiguous (and for e.g. std::list there are not even two contiguous elements).

Ben Voigt
  • 277,958
  • 43
  • 419
  • 720
  • @Artyer: Correct, the same concept holds but it doesn't carry the same name. – Ben Voigt Aug 30 '21 at 21:12
  • Honestly, I didn't really get your point about vector, since `std::vector` resides on the heap and represents a single-dimensional array that perfectly casts to the object pointer. `std::list` has just a different nature. My question is about the plain pointers, not smart ones. Anyway, thanks for your answer! – Denis Petrov Aug 30 '21 at 21:48
  • @DenisPetrov: If you have `T t[][]` then `&t[0][0]` points to all the elements, stored contiguously. For `std::vector> v`, `&v[0][0]` only points to the first row elements, other rows are individual allocations made by their respective vector instances, and not contiguous with each other. – Ben Voigt Aug 30 '21 at 21:57
  • Oh, yes:) It definitely has not a continuous layout. – Denis Petrov Aug 30 '21 at 22:13
  • 1
    The C++ standard specifically notes that first element of an array is not pointer interconvertible with the array. – eerorika Aug 30 '21 at 23:01