There have been multiple questions asking about mmap
and accessing structures in the shared memory while not invoking UB by breaking the strict aliasing rule or violating object lifetimes.
- mmap and C++ strict aliasing rules
- Dealing with undefined behavior when using reinterpret_cast in a memory mapping
- How to properly access mapped memory without undefined behavior in C++
With consensus that this is not generally possible without copying the data.
More generally, over the years here, I have seen countless code snippets involving reinterpret_cast
(or worse) for (de)serialization, breaking those rules. I always recommended std::memcpy
while claiming that the compiler will elide those copies. Can we do better now?
I would like to clarify what is the correct approach to simply interpret a bunch of bytes as another POD type w̲i̲t̲h̲o̲u̲t̲ copying the data in C++20?
There was a proposal P0593R6 which to my knowledge got accepted into C++20.
Based on reading that, I believe the following code is safe:
template <class T>
T* pune(void* ptr) {
// Guaranteed O(1) initialization without overwritting the data.
auto* dest = new (ptr) std::byte[sizeof(T)];
// There is an implicitly created T, so cast is valid.
return reinterpret_cast<T*>(dest);
}
#include <cstring>
#include <array>
#include <fmt/core.h>
struct Foo {
int x;
float y;
};
auto get_buffer() {
Foo foo{.x = 10, .y = 5.0};
std::array<std::byte, sizeof(Foo)> buff;
std::memcpy(buff.data(), &foo, sizeof(foo));
return buff;
}
int main() {
// Imagine the buffer came from a file or mmaped memory,
// compiler does not see the memcpy above.
auto buff = get_buffer();
// There is alive Foo as long as buff lives and
// no new objects are created in there.
auto* new_foo = pune<Foo>(buff.data());
fmt::print("Foo::x={}\n", new_foo->x);
fmt::print("Foo::y={}\n", new_foo->y);
}
Live demo godbolt.
Is this really safe?
Is pune
really O(1)
?
EDIT
Okay, how about using memmove
and still rely on the compiler to do this in O(1)?
template <class T>
T* pune(void* ptr) {
void* dest = new (ptr) std::byte[sizeof(T)];
auto* p = reinterpret_cast<T*>(std::memmove(dest,ptr,sizeof(T)));
return std::launder(p);
}