Могу ли я поместить новый std :: tuple в область отображения памяти и прочитать ее позже? - PullRequest
0 голосов
/ 22 сентября 2018

У меня есть несколько упакованных структур, которые я буду записывать в файл отображения памяти.Все они 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;
}

Ответы [ 2 ]

0 голосов
/ 22 сентября 2018

Вы можете выровнять std::uint8_t buf[100] хранилище, потому что не выровненный доступ является неопределенным поведением:

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

(изначально у вас было 100 байт, это на 100 Tup с).

Когда вы отображаете страницы, они начинаются с границы не менее 4 тыс. На x86.Если ваше хранилище запускается с начала страницы, то это хранилище будет соответствующим образом выровнено для любого выравнивания степени 2 вплоть до 4 тыс.


Я беспокоюсь, что запись членов std::tuple вадрес моего сопоставленного региона, а затем последующее преобразование этого адреса обратно в std::tuple будет повреждено.

Пока приложения, взаимодействующие через сопоставленную память, используют один и тот же ABI, который работает, как и ожидалось.

0 голосов
/ 22 сентября 2018
Tup* p = reinterpret_cast<Tup*>(buf) + size;
new (p) Tup(std::move(t));

- неопределенное поведение, поскольку buf может быть неправильно выровнено для Tup.Правильный способ сделать это - использовать std::aligned_storage.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...