Diomidis Spinellis
Diomidis Spinellis

Reputation: 19345

Incorrect vector size with Boost allocators

The following program allocates the memory for c, an object of type C, within the space of a memory-mapped file. Adding a single character to the vector contained within c changes the vector's reported size from 0 to 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;
}

The problem does not occur when c is allocated on the stack, rather than dynamically.

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

I compile the code on Debian GNU/Linux stretch with Boost 1.62 and g++ 6.3.0-18 with the following command.

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

Upvotes: 1

Views: 127

Answers (2)

sehe
sehe

Reputation: 392954

The allocator returns raw, uninitialized memory.

Indirecting through it as though it pointed to an object of type C is Undefined Behaviour.

You could - of course - actually do the grunt work using placement-new:

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

Note that, likewise, for non-POD (or actually, non-trivially-destructable types) you will have to remember to also call the destructor at the appropriate time(s):

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);

But as you already pointed out the highlevel way avoids new/delete and uses the segment-manager:

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

DEMO

Using

  • find_or_construct so shared objects can be retrieved by name (use anonymous or unique instances if you don't want that; note also that you can instantiate multiple instances through the same names)

  • using allocator_type facilitates using scoped allocator adaptors (think: vector<C>)

  • parameterized C makes it easy to use it with standard allocators and shared memory allocators all the same

  • using the implicit conversion of segment manager pointers and allocator instances

  • hiding the implementation detail offset_ptr (which 90% of the time you do not need to know about)

Live On 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);
}

Prints

0
1

Upvotes: 1

Diomidis Spinellis
Diomidis Spinellis

Reputation: 19345

It seems that moving the constructed object from the default memory pool to the segment one somehow confuses the vector's implementation. Constructing the object directly within the segment memory pool by using the construct method solves the problem.

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

Upvotes: 1

Related Questions