Неправильный размер вектора с аллокаторами Boost

Следующая программа выделяет память для c, объекта типа C, в пространстве отображаемого в память файла. Добавление одного символа к вектору, содержащемуся в c, изменяет сообщаемый размер вектора с 0 на 18446744073709551520.

#include <iostream>
#include <new>
#include <boost/interprocess/managed_mapped_file.hpp>
#include <boost/interprocess/offset_ptr.hpp>
#include <boost/interprocess/allocators/allocator.hpp>
#include <boost/filesystem.hpp>
#include <boost/filesystem/operations.hpp>

using namespace boost::interprocess;

class C;

typedef managed_mapped_file::segment_manager SegmentManager;
typedef allocator<void, SegmentManager> VoidAllocator;
typedef allocator<char, SegmentManager> CharAllocator;
typedef allocator<C, SegmentManager> CAllocator;
typedef offset_ptr<C> CPtr;

class C {
  public:
    std::vector<char, CharAllocator> data;

    C(const VoidAllocator &voidAlloc) : data(voidAlloc) {}

    void add_char() {
      std::cout << data.size() << std::endl;
      data.push_back('x');
      std::cout << data.size() << std::endl;
    }
};

int main(int argc, char *argv[]) {
  boost::filesystem::remove_all("file");
  managed_mapped_file segment(create_only, "file", 100000);

  VoidAllocator allocator_instance(segment.get_segment_manager());
  CAllocator alloc_c(allocator_instance);
  CPtr c = alloc_c.allocate_one();
  *c = C(allocator_instance);
  c->add_char();

  return 0;
}

Проблема не возникает, когда c выделяется в стеке, а не динамически.

C c(allocator_instance);
c.add_char();

Я компилирую код на Debian GNU/Linux stretch с Boost 1.62 и g++ 6.3.0-18 с помощью следующей команды.

g++ -Wall -pthread  -lboost_system -lboost_filesystem t.cpp -o t

person Diomidis Spinellis    schedule 10.01.2018    source источник


Ответы (2)


Распределитель возвращает необработанную, неинициализированную память.

Косвенное обращение через него, как если бы оно указывало на объект типа C, является неопределенным поведением.

Вы могли бы, конечно, на самом деле выполнить черновую работу с помощью Placement-new:

CPtr c = alloc_c.allocate_one();
new (&*c) C(allocator_instance);

Обратите внимание, что аналогичным образом для не-POD (или фактически нетривиально разрушаемых типов) вам нужно будет не забыть также вызвать деструктор в соответствующее время:

CPtr c = alloc_c.allocate_one();

new (&*c) C(allocator_instance);
*c = C(allocator_instance);

c->add_char();

c->~C();
alloc_c.deallocate_one(c);

Но, как вы уже указали, способ высокого уровня избегает создания/удаления и использует менеджер сегментов:

CPtr c = segment.construct<C>("Name") (allocator_instance); 

ДЕМО

С использованием

  • find_or_construct чтобы общие объекты можно было получить по имени (используйте экземпляры anonymous или unique, если вы этого не хотите; также обратите внимание, что вы можете создавать экземпляры нескольких экземпляров с одними и теми же именами)

  • использование allocator_type облегчает использование адаптеров распределителя с областью действия (подумайте: vector<C>)

  • параметризованный C позволяет легко использовать его со стандартными распределителями памяти и разделяемыми распределителями памяти.

  • используя неявное преобразование указателей диспетчера сегментов и экземпляров распределителя

  • скрытие детали реализации offset_ptr (о которой в 90% случаев вам не нужно знать)

Прямой эфир на Coliru

#include <boost/interprocess/allocators/allocator.hpp>
#include <boost/interprocess/managed_mapped_file.hpp>
#include <boost/interprocess/containers/vector.hpp>
#include <iostream>

namespace bip = boost::interprocess;

namespace Shared {
    using Segment         = bip::managed_mapped_file;
    using Manager         = Segment::segment_manager;
    template <typename T = void>
        using Alloc       = bip::allocator<T, Manager>;
    template <typename T>
        using Vector      = bip::vector<T, Alloc<T> >;

    template <typename Alloc = std::allocator<void> >
    struct C {
        using allocator_type = Alloc;
        bip::vector<char, typename Alloc::template rebind<char>::other> data;

        C(Alloc alloc = {}) : data(alloc) {}

        void add_char() {
            std::cout << data.size() << std::endl;
            data.push_back('x');
            std::cout << data.size() << std::endl;
        }
    };
}

int main() {
    std::remove("file");
    Shared::Segment mmf(bip::create_only, "file", 1000000);

    using Alloc = Shared::Alloc<>;
    using C = Shared::C<Alloc>;

    auto* c = mmf.find_or_construct<C>("byname")(mmf.get_segment_manager());

    c->add_char();

    //mmf.destroy_ptr(c);
}

Отпечатки

0
1
person sehe    schedule 10.01.2018
comment
Спасибо за подробный ответ, который объясняет поведение, которое я видел. Не могли бы вы объяснить, где указано, что взаимодействие с необработанной памятью, как если бы она указывала на объект, приводит к неопределенному поведению? Я думал, что новое размещение было добавлено в качестве оптимизации, а не для защиты от неопределенного поведения. - person Diomidis Spinellis; 10.01.2018
comment
Если тип нельзя построить тривиально, переинтерпретация унифицированной памяти как объекта типа T нарушает инварианты, за создание которых отвечает конструктор. В частности, вызов функций-членов для этого объекта (как в вашем примере operator=) может иметь любой результат, включая поедание собаки вашего соседа, - person sehe; 11.01.2018

Кажется, что перемещение построенного объекта из пула памяти по умолчанию в сегментный как-то запутывает реализацию вектора. Построение объекта непосредственно в пуле памяти сегмента с использованием метода construct решает проблему.

CPtr c = segment.construct<C>(anonymous_instance) (allocator_instance);
person Diomidis Spinellis    schedule 10.01.2018