我有一些打包的结构,将要写入内存映射文件。它们都是POD。

为了适应我正在做的一些通用编程,我希望能够编写包含几个压缩结构的std::tuple

我担心将std::tuple的成员写入到我的映射区域的地址,然后将该地址重新转换回std::tuple将会中断。

我编写了一个小型的示例程序,它似乎确实起作用,但是我担心自己的行为不确定。

这是我的结构:

struct Foo
{
    char    c;
    uint8_t pad[3];
    int     i;
    double  d;

} __attribute__((packed));

struct Bar
{
    int     i;
    char    c;
    uint8_t pad[3];
    double  d;

} __attribute__((packed));

我定义了这些结构的std::tuple:
using Tup = std::tuple<Foo, Bar>;

为了模拟内存映射文件,我创建了一个带有一些内联存储和大小的小对象:

添加元组时,它将使用new放置在嵌入式存储中构造该元组。
struct Storage
{
    Tup& push_back(Tup&& t)
    {
        Tup* p = reinterpret_cast<Tup*>(buf) + size;
        new (p) Tup(std::move(t));

        size += 1;

        return *p;
    }

    const Tup& get(std::size_t i) const
    {
        const Tup* p = reinterpret_cast<const Tup*>(buf) + i;
        return *p;
    }

    std::size_t  size = 0;
    std::uint8_t buf[100];
};

为了模拟写入文件然后再次读取的过程,我创建了一个Storage对象,对其进行填充,复制,然后使原始对象超出范围。
Storage s2;

// scope of s1
{
    Storage s1;

    Tup t1 = { Foo { 'a', 1, 2.3 }, Bar { 2, 'b', 3.4 } };
    Tup t2 = { Foo { 'c', 3, 5.6 }, Bar { 4, 'd', 7.8 } };

    Tup& s1t1 = s1.push_back(std::move(t1));
    Tup& s1t2 = s1.push_back(std::move(t2));

    std::get<0>(s1t1).c = 'x';
    std::get<1>(s1t2).c = 'z';

    s2 = s1;
}

然后,我使用Storage::get读取我的元组,它只是内联存储的reinterpret_cast<Tup&>
const Tup& s2t1 = s2.get(0);

当我访问元组中的结构时,它们具有正确的值。

另外,运行valgrind不会引发任何错误。
  • 我在做什么是定义的行为?
  • 如果元组最初是在此处新放置的(放入一个将被关闭然后再重新映射并重新读取的文件中),则从我的嵌入式存储中将reinterpret_cast转换为std::tuple是否安全?

  • 内存映射文件:

    我使用的实际存储是转换为boost::mapped_region的结构。

    结构为:
    struct Storage
    {
        std::size_t  size;
        std::uint8_t buf[1]; // address of buf is beginning of Tup array
    };
    

    我将其转换如下:
    boost::mapped_region region_ = ...;
    Storage* storage = reinterpret_cast<Storage*>(region_.get_address());
    

    以下答案中提到的对齐问题会不会成为问题?

    下面的完整示例:
    #include <cassert>
    #include <cstdint>
    #include <tuple>
    
    struct Foo
    {
        char    c;
        uint8_t pad[3];
        int     i;
        double  d;
    
    } __attribute__((packed));
    
    struct Bar
    {
        int     i;
        char    c;
        uint8_t pad[3];
        double  d;
    
    } __attribute__((packed));
    
    using Tup = std::tuple<Foo, Bar>;
    
    struct Storage
    {
        Tup& push_back(Tup&& t)
        {
            Tup* p = reinterpret_cast<Tup*>(buf) + size;
            new (p) Tup(std::move(t));
    
            size += 1;
    
            return *p;
        }
    
        const Tup& get(std::size_t i) const
        {
            const Tup* p = reinterpret_cast<const Tup*>(buf) + i;
            return *p;
        }
    
        std::size_t  size = 0;
        std::uint8_t buf[100];
    };
    
    int main ()
    {
        Storage s2;
    
        // scope of s1
        {
            Storage s1;
    
            Tup t1 = { Foo { 'a', 1, 2.3 }, Bar { 2, 'b', 3.4 } };
            Tup t2 = { Foo { 'c', 3, 5.6 }, Bar { 4, 'd', 7.8 } };
    
            Tup& s1t1 = s1.push_back(std::move(t1));
            Tup& s1t2 = s1.push_back(std::move(t2));
    
            std::get<0>(s1t1).c = 'x';
            std::get<1>(s1t2).c = 'z';
    
            s2 = s1;
        }
    
        const Tup& s2t1 = s2.get(0);
        const Tup& s2t2 = s2.get(1);
    
        const Foo& f1 = std::get<0>(s2t1);
        const Bar& b1 = std::get<1>(s2t1);
    
        const Foo& f2 = std::get<0>(s2t2);
        const Bar& b2 = std::get<1>(s2t2);
    
        assert(f1.c == 'x');
        assert(f1.i == 1);
        assert(f1.d == 2.3);
    
        assert(b1.i == 2);
        assert(b1.c == 'b');
        assert(b1.d == 3.4);
    
        assert(f2.c == 'c');
        assert(f2.i == 3);
        assert(f2.d == 5.6);
    
        assert(b2.i == 4);
        assert(b2.c == 'z');
        assert(b2.d == 7.8);
    
        return 0;
    }
    

    最佳答案

    您可能想对齐std::uint8_t buf[100]存储,因为未对齐的访问是未定义的行为:

    aligned_storage<sizeof(Tup) * 100, alignof(Tup)>::type buf;
    

    (最初您有100个字节,这是100个Tup)。

    当您映射页面时,它们开始于x86上至少4k的边界。如果您的存储从页面开头开始,则该存储适合于任何功率2对齐(最大4k)的对齐。



    只要通过映射内存进行通信的应用程序使用相同的ABI,它就会按预期工作。

    10-07 19:32
    查看更多