Consider the following excerpt from wg21.link/p0593:
A call to memmove behaves as if it
copies the source storage to a temporary area
implicitly creates objects in the destination storage, and then
copies the temporary storage to the destination storage.
This permits memmove to preserve the types of trivially-copyable objects, or to be used to reinterpret a byte representation of one object as that of another object.
struct A { int n; };
auto a = A{1};
void* p1 = &a;
alignas(A) char buf[8];
// p1 is of void*, so it shouldn't be assumed pointing to an object of A.
auto p2 = static_cast<A*>(memmove(buf, p1, sizeof(A)));
p2->n = 2; // Is this technically UB since C++20?
auto fn = [&] { return memmove(buf, p1, sizeof(A)); };
// Must it be `auto p3 = std::start_lifetime_as<A>(fn());` here?
auto p3 = static_cast<A*>(fn()); // fn is not blessed by the standard as memmove.
p3->n = 3; // Is this technically UB since C++20?
This is UB in 3 levels:
char
was not the proper element type for a void buffer. Either use unsigned char
, or C++17 std::byte
(<cstddef>) whose main use case is void buffer:std::array
< std::byte
, 2 * sizeof(A)
> buffer {};
alignas(A) std::array
< std::byte
, sizeof(A)
> static_align_buffer {};
std::array
< std::byte
, 2 * sizeof(A)
> buffer {};
auto volume = size(buffer);//mutable
auto dynamic_align_buffer
= std::align
( alignof(A), sizeof(A)
, data(buffer), volume );
std::memmove
was not guaranteed to be an implicitly created before LWG4064. So, before C++26, you have to invoke C++23 std::start_lifetime_as<A>
on some platforms, and static_cast<A*>
on others, or just use the destination argument to std::memmove
and std::launder
:A a {1};
std::memmove
( dynamic_align_buffer,
, &a, sizeof(a) );
auto p2
= std::launder
( static_cast<A*>
( dynamic_align_buffer ) );
But all the effort above is futile in presence of C++20 <bit>
. It's considered unsafe and code smell to use either of std::memmove
, std::memcopy
, std::memset
. These functions serve four fundamental algorithms in C which have well-optimized generic counterparts in C++. Because if the operand type is not trivial
, the mem[*]
functions result in resource management UB(double free, use after free, leak). The four use cases are:
std::ranges::copy
( source
| std::views::take
( std::ranges::distance
( dest ) ) // take
, std::ranges::begin(dest) );
std::ranges::move
( source
| std::views::take
( std::ranges::distance
( dest ) ) // take
, std::ranges::begin(dest) );
std::ranges::fill(dest, value);
static_assert
( sizeof(dest_t)
== sizeof(source) );
dest_t dest
= std::bit_cast<dest_t>
( source );
There's no other general use case for mem[*]
functions. These functions should only be invoked by the platform, not user or 3rd party libraries.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With