【问题标题】:Do C++ Objects (Standard Definition) persist in memory map files?C++ 对象(标准定义)是否保留在内存映射文件中?
【发布时间】:2021-10-18 15:18:31
【问题描述】:

Dealing with large data binary files启发的问题

链接到Object

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

程序(2)将上述文件映射到内存中,并尝试通过reinterpret_cast访问对象。

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

如果这是在 2 个进程之间尝试的,不是使用文件,而是使用共享进程内存是否合法?

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

【问题讨论】:

  • 对象生命周期的范围在正在执行的 C++ 程序中。程序 (1) 看到的对象不是程序 (2) 看到的对象。但是该对象在程序 (2) 中根本不存在,因为它的生命周期从未开始。
  • 如果Object 不是太大并且是标准布局对象,您可以将memcpy 表示从内存映射文件到对象的实际实例。
  • @FrançoisAndrieux 这将您可以使用的类型限制为可轻松复制的类型en.cppreference.com/w/cpp/types/is_trivially_copyable
  • @PepijnKramer 我不确定简单可复制是否足够,因为数据来自可能使用不同实现编译的另一个进程。标准布局类型在编程语言之间是兼容的,所以应该没问题。简单可复制的类型在不同实现中可能没有相同的布局。
  • 是的,序列化就像那样很难(尽管我假设两个进程都使用相同的设置进行编译)。 @Richard Critten 查看了 IPC(进程间通信)协议,它们以标准化格式将类型从一个进程发送到另一个进程。 (例如 protobuf)

标签: c++ language-lawyer


【解决方案1】:

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

C++ 对象主要由它们的lifetime 定义,其作用域仅限于程序。

因此,如果您想从原始存储中回收一个对象,则程序 (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();
}

但你也不能这样做。编译器被允许(并且有时确实如此)假设这样的构造会破坏内存。详情请见C++ placement new after memset,以及演示。

如果您想从给定的存储表示初始化一个对象,您必须使用memcpy() 或等效的:

void foo(char* raw_data) {
  MyObj obj;

  static_assert(std::is_standard_layout_v<MyObj>);
  std::memcpy(&obj, raw_data, sizeof(MyObj));
}

附录:可以通过在创建对象后使用其原始内容重新填充内存来完成所需的reinterpret_cast&lt;&gt; 的等效操作(受IOC proposal 的启发)。

#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);
}

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

godbolt

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2010-12-09
    • 2011-11-01
    • 2014-03-04
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-12-08
    相关资源
    最近更新 更多