Question inspired by Dealing with large data binary files
Link to Object
Program (1) creates a memory-mapped file and writes some Objects (C Standard definition) to it, closes the file and exits.
Program (2) maps the above file into memory and tries to access the Objects via reinterpret_cast
.
Is this legal by the Standard as the object representations have not changed and the Objects still exist in the file ?
If this was attempted between 2 processes, not with a file, but using shared process memory is this legal ?
Note - this question is not about storing or sharing local virtual addresses as this is obviously a bad thing.
CodePudding user response:
No, objects do not persist this way.
C objects are defined primarily by their lifetime, which is scoped to the program.
So if you want to recycle an object from raw storage, there has to be a brand new object in program (2) with its own lifetime. reinterpret_cast
'ing memory does not create a new object, so that doesn't work.
Now, you might think that inplace-newing an object with a trivial constructor at that memory location could do the trick:
struct MyObj {
int x;
int y;
float z;
};
void foo(char* raw_data) {
// The content of raw_data must be treated as being ignored.
MyObj* obj = new (raw_data) MyObj();
}
But you can't do that either. The compiler is allowed to (and demonstrably does sometimes) assume that such a construction mangles up the memory. See C placement new after memset for more details, as well as a demonstration.
If you want to initialize an object from a given storage representation, you must use memcpy()
or an equivalent:
void foo(char* raw_data) {
MyObj obj;
static_assert(std::is_standard_layout_v<MyObj>);
std::memcpy(&obj, raw_data, sizeof(MyObj));
}
Addendum: It is possible to do the equivalent of the desired reinterpret_cast<>
by restomping the memory with its original content after creating the object (inspired by the IOC proposal).
template<typename T>
T* start_lifetime_as(void *p) {
static_assert(std::is_standard_layout_v<T>);
assert((std::uintptr_t)p % alignof(T) == 0);
auto aligned_p = std::assume_aligned<alignof(T)>(p);
T tmp;
std::memcpy(&tmp, aligned_p, sizeof(T));
T* t_ptr = new (aligned_p) T{};
std::memcpy(t_ptr , &tmp, sizeof(T));
return std::launder<T>(t_ptr);
}
void foo(char* raw_data) {
MyObj* obj = start_lifetime_as<MyObj>(raw_data);
}
This should be well-defined in C 11 and up as long as that memory location only contains raw data and no prior object. Also, from cursory testing, it seems like compilers do a good job at optimizing that away.
see on godbolt