std::launder example has this block of code:
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
What? Example from std::aligned_storage
makes it seem to me like that's not the case:
std::aligned_storage_t<sizeof(T), alignof(T)> data[N];
// Access an object in aligned storage
const T& operator[](std::size_t pos) const
{
// Note: std::launder is needed after the change of object model in P0137R1
return *std::launder(reinterpret_cast<const T*>(&data[pos]));
}
Here's where confusion comes in: &data[pos]
is just &data[pos][0]
, because &p
== &p[0]
where p
is an array.
How come this is okay if std::aligned_storage
can hardly if at all be implemented in any other way but something like alignas(T) std::byte[sizeof (T)];
? Does the array being in a struct somehow magically make it okay? Why?
Let's say I have
template <typename T>
using uninitialized = alignas(T) std::byte[sizeof (T)];
constexpr auto N = 5;
uninitialized<int> array[N];
for (std::size_t i = 0; i < N; i ) {
new (&array[i]) int(i);
}
Now what? Isn't any cast like
auto laundered_value_ptr = std::launder(reinterpret_cast<int*>(&array[i]));
identical to the first example? What about this:
auto laundered_array_ptr = std::launder(reinterpret_cast<int*>(array));
laundered_array_ptr[0] = 9;
laundered_array_ptr[2] = 76;
If I follow, it seems like there's no way to use this memory correctly, because using a std::byte(*)[sizeof (int)]
means that basically anything around it is reachable, and following first example, everything written in this question is may aswell be UB.
I compiled these examples using g -g -O0 -Wextra -Wall -Wpedantic -std=c 20 -fsanitize=undefined -fsanitize=address
and curiously had gotten not even a warning which leaves me completely stumped.
I doubt this matters at all, but here's the version of compiler that I am using.
g (GCC) 12.1.1 20220730
CodePudding user response:
You wrote:
&data[pos]
is just&data[pos][0]
, because&p
==&p[0]
wherep
is an array.
In fact, that's not true. If p
is an array (call its type T[N]
), the expression &p == &p[0]
will not compile. The left-hand side has type T(*)[N]
, and the right-hand side has type T*
. These cannot be compared directly, any more than you could compare an int*
with a char*
.
However, static_cast<void*>(&p) == static_cast<void*>(&p[0])
will be true.
The two pointers represent the same address, but they are not substitutable for each other.
&data[pos]
is a pointer to one element of data
, and all other elements of data
are reachable through it. On the other hand, (supposing that std::aligned_storage_t
is an alias for an array of unsigned char
) &data[pos][0]
points only to a single element of an element of data
; it does not point to the entire element of data
. So all other elements of data[pos]
are reachable through it, but other elements of data
are not.
Since all elements of data
are reachable through &data[pos]
, it follows that all bytes in data
are reachable through &data[pos]
. After this pointer is converted to const T*
, the other T
's that are stored inside the other elements of data
are also reachable from the resulting pointer, but the bytes of those T
's were also reachable from the original pointer, so the preconditions of std::launder
are met.
If we used &data[pos][0]
as the argument to reinterpret_cast
, only the bytes in the single element data[pos]
would be reachable from that pointer, and the preconditions would not be met.