代码之家  ›  专栏  ›  技术社区  ›  Steve Lorimer

我可以将一个新的std::tuple放置到内存映射区域中,稍后再读取它吗?

  •  3
  • Steve Lorimer  · 技术社区  · 7 年前

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

    为了适应我正在做的一些泛型编程,我希望能够编写一个 std::tuple

    我担心 std::元组 就要破裂了。

    但我担心我的行为不明确。

    以下是我的结构:

    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::元组 在这些结构中:

    using Tup = std::tuple<Foo, Bar>;
    

    添加元组时,它使用placement 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 从内联存储到 如果元组最初的位置是在那里更新的(到一个文件中,该文件将被关闭,然后重新映射和读取)?

    内存映射文件:

    这个 实际的 我使用的存储是在 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;
    }
    
    2 回复  |  直到 7 年前
        1
  •  3
  •   Maxim Egorushkin    7 年前

    std::uint8_t buf[100]

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

    (原来是100字节,这是100字节 Tup s) 是的。


    我担心 std::tuple 到我的映射区域的地址,然后将该地址强制转换回 std::元组

    只要通过映射内存进行通信的应用程序使用相同的ABI,就可以正常工作。

        2
  •  2
  •   marc_s MisterSmith    7 年前
    Tup* p = reinterpret_cast<Tup*>(buf) + size;
    new (p) Tup(std::move(t));
    

    是未定义的行为 buf Tup . 正确的方法是使用 std::aligned_storage .