2

I'm trying to understand the following snippet from CPP reference. Can someone explain in a little more detail why x2[1] is unreachable from source ? Can't we reach it via &x2[0][0] + 10 for example?

int x2[2][10];
auto p2 = std::launder(reinterpret_cast<int(*)[10]>(&x2[0][0])); 
// Undefined behavior: x2[1] would be reachable through the resulting pointer to x2[0]
// but is not reachable from the source
user17732522
  • 53,019
  • 2
  • 56
  • 105
user3882729
  • 1,339
  • 8
  • 11

1 Answers1

2

The reachability condition basically asks whether it is possible to access a given byte of memory via pointer arithmetic and reinterpret_cast from a given pointer. The technical definition, which is effectively the same, is given on the linked cppreference page:

(bytes are reachable through a pointer that points to an object Y if those bytes are within the storage of an object Z that is pointer-interconvertible with Y, or within the immediately enclosing array of which Z is an element)

x2 is an array of 2 arrays of 10 arrays of int. Let's suppose we call the two arrays a and b.

&x2[0][0] is an int* pointing to the first element of a.

&x2[0][0] + 10 is an int* pointer one-past the last element a. The address of this pointer value is also the address at which b begins. However one cannot obtain a pointer to b or one of its elements via reinterpret_cast since &x2[0][0] + 10 doesn't point to any object that is pointer-interconvertible with b or one of its elements.

In terms of the technical definition, the only object pointer-interconvertible with the first element of a is the object itself. Therefore the reachable bytes from a pointer to the first element of a are only the bytes of a, which is the array immediately enclosing this object.

Therefore the reachable bytes through &x2[0][0] are only those of the array a, not including b. E.g. *(&x2[0][0] + 10) = 123; has undefined behavior.

However if std::launder were to return a pointer to a (of type int(*)[10]), then (p2+1)[i] would be a way to access all elements of b. Or in terms of the technical definition, the array immediately enclosing the object p2 points to would be x2, so that all bytes of x2 are reachable.

This means after the std::launder bytes that weren't reachable before would become reachable. Therefore the call has undefined behavior.

user17732522
  • 53,019
  • 2
  • 56
  • 105
  • It's not reachable through the source since ```&x2[0][0] + 10``` is an ```int *``` vs. ```int (*)[10]``` ? IOW, the former is not pointer-convertible to the latter. – user3882729 Mar 26 '22 at 20:41
  • @user3882729 Pointer-interconvertibility is a property between objects. The type of pointers doesn't determine the type of the object they are pointing to. So the problem is not the type, but rather that `&x2[0][0] + 10` doesn't point to any object at all, so trivially it cannot be pointing to any object which is pointer-interconvertible with any other object of any type. – user17732522 Mar 26 '22 at 20:44
  • W.r.t. pointing to a valid object this [answer](https://stackoverflow.com/questions/6015080/c-c-is-this-undefined-behavior-2d-arrays#comment6953450_6015221) mentions doing any arithmetic beyond pointer increment is UB. Perhaps access of ```&x2[0][0] + k``` is only defined for ```k < 10``` and for all other values does not point to a valid object consequently making them unreachable – user3882729 Mar 26 '22 at 21:52
  • 1
    @user3882729 `k>10` is UB, but `k=10` is allowed. However, the resulting pointer is not a pointer pointing to an object. It is a one-past-the-object pointer and may not be dereferenced. – user17732522 Mar 26 '22 at 22:16
  • The standard itself didn't mention this example, and I think `x2[1]` is reachable after the `reinterpret_cast` instead of after `std::launder`. So there's no UB. The precondition "All bytes of storage that would be reachable through the result are reachable through p" is about implicit conversions like `std::launder(&derived)` – Tuff Contender Jul 19 '22 at 11:01
  • @TuffContender `std::launder(&derived)` is equivalent to `Base* x = &derived; std::launder(x)` and this `std::launder` call is redundant because `x` must already be pointing to the base class subobject after the conversion. I am not sure where you consider reachability to come in. A `reinterpret_cast` does not change the pointer value, so the resulting pointer still points to the array element instead of the array, because array elements and arrays are not pointer-interconvertible. See https://timsong-cpp.github.io/cppwp/n4868/basic#compound-note-4 – user17732522 Jul 19 '22 at 11:29
  • @user17732522 Thanks a lot, now I see the precise meaning of pointer values. – Tuff Contender Aug 18 '22 at 11:53