C++ 对象(标准定义)是否保留在内存映射文件中?

Ric*_*ten 2 c++ language-lawyer

问题的启发来自处理大型数据二进制文件

链接到对象

程序(1)创建一个内存映射文件并向其中写入一些对象(C++标准定义),关闭文件并退出。

程序(2)将上述文件映射到内存中并尝试通过reinterpret_cast.

根据标准,这是否合法,因为对象表示没有更改并且对象仍然存在于文件中?

如果在两个进程之间尝试这样做,而不是使用文件,而是使用共享进程内存,这合法吗?

注意 - 这个问题与存储或共享本地虚拟地址无关,因为这显然是一件坏事。

小智 8

不,对象不会以这种方式持久存在。

C++ 对象主要由其生命周期定义,生命周期的范围仅限于程序。

因此,如果你想从原始存储中回收一个对象,程序(2)中必须有一个具有自己生命周期的全新对象。reinterpret_cast'ing内存不会创建新对象,所以这是行不通的。

现在,您可能认为在该内存位置用简单的构造函数就地新建一个对象可以解决这个问题:

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();
}
Run Code Online (Sandbox Code Playgroud)

但你也不能这样做。编译器可以(并且有时确实如此)假设这样的构造会破坏内存。有关更多详细信息以及演示,请参阅memset 之后的 C++ 放置新内容。

如果要从给定的存储表示形式初始化对象,则必须使用memcpy()或等效项:

void foo(char* raw_data) {
  MyObj obj;

  static_assert(std::is_standard_layout_v<MyObj>);
  std::memcpy(&obj, raw_data, sizeof(MyObj));
}
Run Code Online (Sandbox Code Playgroud)

附录:可以reinterpret_cast<>通过在创建对象后用其原始内容恢复内存来完成所需的等效操作(受到IOC 提案的启发)。

#include <type_traits>
#include <cstring>
#include <memory>

template<typename T> 
T* start_lifetime_as(void *p) 
  requires std::is_trivially_copyable_v<T> {
  
  constexpr std::size_t size = sizeof(T);
  constexpr std::size_t align = alignof(T);

  auto aligned_p = std::assume_aligned<align>(p);

  std::aligned_storage_t<size, align> tmp;
  std::memcpy(&tmp, aligned_p, size);

  T* t_ptr = new (aligned_p) T{};
  std::memcpy(t_ptr , &tmp, size);

  return std::launder<T>(t_ptr);
}


void foo(char* raw_data) {
  MyObj* obj = start_lifetime_as<MyObj>(raw_data);
}
Run Code Online (Sandbox Code Playgroud)

只要该内存位置仅包含原始数据且不包含先前的对象,就应该在 C++11 及更高版本中明确定义这一点。另外,从粗略的测试来看,编译器似乎在优化方面做得很好。

参见Godbolt