28

Consider the following scenario:

std::array<int, 8> a;
auto p = reinterpret_cast<int(*)[8]>(a.data());
(*p)[0] = 42;

Is this undefined behavior? I think it is.

  • a.data() returns a int*, which is not the same as int(*)[8]

  • The type aliasing rules on cppreference seem to suggest that the reinterpret_cast is not valid

  • As a programmer, I know that the memory location pointed by a.data() is an array of 8 int objects

Is there any rule I am missing that makes this reinterpret_cast valid?

Vittorio Romeo
  • 90,666
  • 33
  • 258
  • 416
  • `std::array` is required to be a contiguous container. Thus, the question boils down to whether an `int * p` such that `[p; p+N)` is known to be a valid pointer range can be casted to `int[N]`. – lisyarus Jan 25 '18 at 14:04
  • https://stackoverflow.com/questions/47924103/pointer-interconvertibility-vs-having-the-same-address – n. m. could be an AI Jan 25 '18 at 20:35
  • Misleading title. It's never UB to do a pointer cast. The potential problem would be accessing memory via dereferencing the result of the cast – M.M Jan 25 '18 at 22:08

2 Answers2

15

An array object and its first element are not pointer-interconvertible*, so the result of the reinterpret_cast is a pointer of type "pointer to array of 8 int" whose value is "pointer to a[0]"1.In other words, despite the type, it does not actually point to any array object.

The code then applies the array-to-pointer conversion to the lvalue that resulted from dereferencing such a pointer (as a part of the indexing expression (*p)[0])2. That conversion's behavior is only specified when the lvalue actually refers to an array object3. Since the lvalue in this case does not, the behavior is undefined by omission4.


*If the question is "why is an array object and its first element not pointer-interconvertible?", it has already been asked: Pointer interconvertibility vs having the same address.

1See [expr.reinterpret.cast]/7, [conv.ptr]/2, [expr.static.cast]/13 and [basic.compound]/4.

2See [basic.lval]/6, [expr.sub] and [expr.add].

3[conv.array]: "The result is a pointer to the first element of the array."

4[defns.undefined]: undefined behavior is "behavior for which this document imposes no requirements", including "when this document omits any explicit definition of behavior".

T.C.
  • 133,968
  • 17
  • 288
  • 421
  • Are you saying that `std::array` doesn't contain any `int[8]` subobject, or are you saying the pointer doesn't point to that subobject? In either case, I think that would mean that `a.data() + 3` is also invalid, and we know that's supposed to be valid. –  Jan 25 '18 at 15:50
  • 1
    @hvd I'm saying that the pointer points to a subobject of that `int[8]` subobject; i.e., it's still pointing to an element of the array, not the array itself. You cannot obtain a pointer to the array from a pointer to an element with `reinterpret_cast`. – T.C. Jan 25 '18 at 17:57
  • Okay, when I get back on my computer I'll check to see if I can find anything to counter that, and if not, delete my answer. Meanwhile, your logic would suggest that still, `std::launder` is enough to make it valid. Do you agree? –  Jan 25 '18 at 18:20
  • 1
    @hvd Only if you meet the no-extra-reachability requirement, which will depend on the array at issue. – T.C. Jan 25 '18 at 18:25
  • In the OP's case, that should not be an issue, as far as I can tell. Every byte of the `int[8]` subobject was already accessible through `data()`. –  Jan 25 '18 at 18:30
  • 2
    @hvd The issue is whether you can access beyond that subobject with the resulting pointer. E.g., if `std::array` is something like `struct {int e[8]; char dummy;};` then a pointer to that `int[8]` is pointer-interconvertible with a pointer to the entire struct and so can reach `dummy`, which makes the `launder` undefined. – T.C. Jan 25 '18 at 18:32
  • Following this line of thought, is it impossible to violate strict aliasing rules with reinterpret cast except with unions? – Passer By Jan 25 '18 at 20:06
  • How can you though, when the pointer is unchanged by the cast unless there is interconvertibility? – Passer By Jan 25 '18 at 20:22
  • 2
    @PasserBy `int i; *reinterpret_cast(&i) = 1;` attempts to modify `i` through an lvalue of type `float` that refers to `i`. – T.C. Jan 25 '18 at 20:26
  • That `char dummy;` would pretty much require malice on the implementer's part, but true, the standard doesn't seem to prohibit it... –  Jan 25 '18 at 21:03
7

Yes the behaviour is undefined.

int* (the return type of a.data()) is a different type from int(*)[8], so you are breaking strict aliasing rules.

Naturally though (and this is more for the benefit of future readers),

int* p = a.data();

is perfectly valid, as is the ensuing expression p + n where the integral type n is between 0 and 8 inclusive.

Bathsheba
  • 231,907
  • 34
  • 361
  • 483
  • 5
    I am casting the `.data()` though, not the array itself. – Vittorio Romeo Jan 25 '18 at 13:32
  • @VittorioRomeo: Oops. Corrected, the answer still holds though. – Bathsheba Jan 25 '18 at 13:33
  • in 99.9999% of the cases I see where this might be useful. I would suggest using a [`gsl::span`](https://github.com/Microsoft/GSL) to solve the problem instead – Mgetz Jan 25 '18 at 13:34
  • 2
    I think this answer still is misleading or has a typo: *"`int*` is a completely different type from `std::array*`" - I am not casting to `std::array*`, but to `int(*)[8]` instead. – Vittorio Romeo Jan 25 '18 at 13:49
  • @VittorioRomeo: One more edit - I'm inclined to tin this. – Bathsheba Jan 25 '18 at 13:50
  • 1
    I don't think it's a strict aliasing violation to use lvalue of type `int` to access an object of type `int` (which happens to be a member of an array) – M.M Jan 25 '18 at 22:12