【问题标题】:Map a layout onto memory address将布局映射到内存地址
【发布时间】:2020-06-18 13:35:27
【问题描述】:

在 C++ 中,有没有办法将我想要的布局“映射”到内存数据上,无需复制它?

即有一个void* buffer,我知道它的布局:

  • byte1: uint8_t
  • byte2-3: uint16_t
  • byte4: uint8_t

我知道我可以创建一个结构,并将数据 memcpy 到结构中,然后我可以将值作为结构的字段。

但是有没有办法在不复制的情况下实现这一点?数据已经存在,我只需要获取一些字段,我正在寻找一种方法来帮助布局。

(我可以有一些static ints 用于内存偏移,但我希望有一些更通用的)。

即:我会有更多“布局”,并根据原始数据的类型映射适当的布局并访问仍指向原始数据的字段。

我知道我可以将结构指向数据,这很简单:

struct message {
    uint8_t type;
};
struct request:message {
    uint8_t rid;
    uint8_t other;
};
struct response:message {
    uint8_t result;
};

vector<uint8_t> data;
data.push_back(1); //type
data.push_back(10);
data.push_back(11);
data.push_back(12);
data.push_back(13);

struct request* ptrRequest;

ptrRequest = (struct request*)&data[1];
cout << (int)ptrRequest->rid; //10
cout << (int)ptrRequest->other; //11

但我想要实现的是有一个带有布局的地图,即:

map<int, struct message*> messagetypes;

但我不知道如何进行,因为放置需要一个新对象,而且如果地图只存储基指针,则转换也很有挑战性。

【问题讨论】:

  • 我不确定这里的严格别名规则,但如果结构被正确打包,那么你可以将它转换为指向结构的指针。
  • @Someprogrammerdude 投射指向结构的指针当前是 UB:open-std.org/jtc1/sc22/wg21/docs/papers/2020/…

标签: c++ memory struct


【解决方案1】:

如果您的布局结构是 POD,您可以在没有初始化的情况下进行放置 new-expression,这可用作对象创建标记。例如:

#include <new> // Placement new.
// ...
uint8_t* data = ...; // Read from disk, network, or elsewhere.
static_assert(std::is_pod<request>::value, "struct request must be POD.");
request* ptrRequest = new (static_cast<void*>(data)) request;

这仅适用于 POD。这是P0593R6 Implicit creation of objects for low-level object manipulation 中记录的一个长期存在的问题。

如果您的目标架构需要对齐数据,请添加data 指针对齐检查。


正如另一个答案所述,memcpy 可能会被编译器消除,请检查程序集输出。

【讨论】:

    【解决方案2】:

    在 C++ 中,有没有一种方法可以将我想要的布局“映射”到内存数据上,而不需要复制它?

    不,不在标准 C++ 中。

    如果布局与类的布局匹配1,那么你可以做的就是最初将内存数据写入类实例,这样就不需要以后复制.

    如果上述方法不可行,那么您可能会做的是将数据复制(是的,这是 memcopy,但保持这种想法)到该类的自动实例上,然后将自动实例的副本放置新到源数组。一个好的优化器可以看到这些来回的副本不会改变值,并且可以将它们优化掉。这里也需要匹配布局。示例:

    struct data {
        std::uint8_t  byte;
        std::uint8_t  another;
        std::uint16_t properly_aligned;
    };
    
    void* buffer = get_some_buffer();
    if (!std::align(alignof(data), sizeof(data), buffer, space))
        throw std::invalid_argument("bad alignment");
    
    data local{};
    std::memcpy(&local, buffer, sizeof local);
    data* dataptr = new(buffer) data{local};
    std::uint16_t value_from_offset = dataptr->properly_aligned;
    

    https://godbolt.org/z/uvrXS2 注意在生成的程序集中没有对std::memcpy 的调用。

    这里要考虑的一件事是,多字节整数必须具有与 CPU 本机使用的相同字节顺序。因此,数据不能跨系统移植(不同字节字节序)。便携性需要更高级的反序列化。


    1 然而,数据似乎不太可能与类的布局相匹配,因为第二个元素 uint16_t 未与布局开始处的两个 16 位边界对齐.

    【讨论】:

    • 你能给我看一个自动实例的例子吗?我需要基于类型的不同类(或结构)。
    • @Daniel 添加了示例。
    猜你喜欢
    • 2023-03-03
    • 2020-12-06
    • 1970-01-01
    • 1970-01-01
    • 2018-02-20
    • 2018-10-09
    • 2023-03-23
    • 1970-01-01
    • 2013-04-27
    相关资源
    最近更新 更多