15

I would like to create a std::vector in shared memory using the CreateFileMapping() windows API function. I know how to create shared memory and manage it, but how do I assign std::vector to a fixed address in memory? I cannot use boost or other libraries in my case, I am using CBuilder++ 2010. One variant I think is maybe to use

std::vector<int> myVec; 
myVec *mv;
mv = shared_memory_addr ?

But how do I detect the real size of vectors to resize memory?

buddemat
  • 4,552
  • 14
  • 29
  • 49
Sergey
  • 685
  • 2
  • 8
  • 30
  • possible duplicate of [Elegant ways to count the frequency of words in a file](http://stackoverflow.com/questions/4888879/elegant-ways-to-count-the-frequency-of-words-in-a-file) – Nawaz Mar 19 '11 at 18:31
  • Oops... I voted `close` for wrong topic. Wrong tab! – Nawaz Mar 19 '11 at 18:32
  • @Nawaz I have seen the exactly same question, but I can't find it now – BЈовић Mar 19 '11 at 19:11
  • @VJo: This is the topic which I intended to vote for closing : http://stackoverflow.com/questions/5363575/count-how-many-times-each-distinct-word-appears-in-its-input-c – Nawaz Mar 19 '11 at 19:13
  • Do you mean `typedef std::vector myVec;`? – alfC Jul 07 '17 at 00:04

5 Answers5

8

I'd use Boost.Interprocess, which has an explanation of how to do just this: http://www.boost.org/doc/libs/1_38_0/doc/html/interprocess/quick_guide.html#interprocess.quick_guide.qg_interprocess_container

Note that this does not use std::vector<>, which is not suitable for shared-memory use, because it is typically implemented in terms of three pointers (begin, end, capacity, or some equivalents), and addresses will differ between processes. So Boost.Interprocess has its own vector class, which is purpose-built for what you are trying to do.

John Zwinck
  • 239,568
  • 38
  • 324
  • 436
  • The issue of std::vector<>'s pointers is mitigated by mapping the shared memory at the same virtual address in each process. – Adam Mitz Mar 19 '11 at 17:45
  • Thanks, but i can't use boost in my case, because cbuilder 2010 leaks c++ template support which is used in new boost. – Sergey Mar 19 '11 at 17:50
  • @Adam Mitz: Which may not be feasible: `man mmap` explicitly says the use of `MAP_FIXED` is "not recommended." You may find yourself in a situation where the "correct" address range is already mapped, and then you will lose. – John Zwinck Mar 19 '11 at 17:51
  • @Sergey: do you know what the latest version of Boost is that your toolchain does support? Interprocess is a fairly new part of Boost, but not super recent. You might also find that your compiler can handle "enough" of Boost to get Interprocess working, even if it can't handle all of Boost. – John Zwinck Mar 19 '11 at 17:52
  • @Zwinck: CreateFileMapping() is Win32 API so mmap() doesn't play here. If done carefully in processes that you control completely, you can arrange for a common mapping. – Adam Mitz Mar 19 '11 at 17:57
  • Yes, i tryed to compile only interprocess library of boost, but compiler fails at stage where standart objects were declared in some of builder's VCL. – Sergey Mar 19 '11 at 17:57
  • @Adam Mitz: You're right, the OP is using Win32 not POSIX. So let's look at the documentation for MapViewOfFileEx instead: "While it is possible to specify an address that is safe now (not used by the operating system), there is no guarantee that the address will remain safe over time. Therefore, it is better to let the operating system choose the address. In this case, you would not store pointers in the memory mapped file, you would store offsets from the base of the file mapping so that the mapping can be used at any address." So, Microsoft suggests you don't do it, too. – John Zwinck Mar 19 '11 at 18:08
  • Of course it's better if you don't, but always using a fixed address and coordinating the same address between two or more processes are two different things. Besides, this is out of scope for the question. – Adam Mitz Mar 19 '11 at 18:11
  • Fair enough. Here be dragons. – John Zwinck Mar 19 '11 at 18:12
5

Actually, you have to do both: use placement new to construct the std::vector instance in shared memory AND use a custom allocator to make the vector place its data within the shared memory as well.

Keep in mind that you need to synchronize any access to the vector (except if you need only read access) - std::vector is not generally thread-safe and doesn't declare any of its members volatile, which makes simultaneous access out of the compiler's scope - as it happens in a shared memory region - extremely dangerous.

... after all, I wouldn't do it. Shared memory is a very low-level, very tricky concept, it doesn't fit well with high-level data containers such as std::vector, in a language that (as of cpp03) doesn't provide good builtin solutions for concurrency problems and that is not aware that something like shared memory exists.

... it might even trigger undefined behaviour: while std::vector generally uses its allocator to fetch storage for its elements, it is (as far as I know) allowed to allocate further memory (i.e. for internal purposes, whatever that may be) using malloc or any other allocation strategy (I think Microsoft's std::vector implementation does that in debug builds) ... these pointers would only be valid for one side of the memory mapping.

To avoid the std::vector, I'd simply allocate sufficient memory in the mapped range upfront and use a simple counter to keep the number of valid elements. That should be safe.

Alexander Gessler
  • 45,603
  • 7
  • 82
  • 122
  • 2
    AND you would need to make sure the mappings occur at the same address. Good point about synchronization. – John Zwinck Mar 19 '11 at 17:50
  • Thanks Alexander, yes i am looking at two possible answers: to use one std::vector in each process attach of my dll i can use named pipes (which is to create server thread and client processes) or use shared memory. The shared memory approach is less code, but for now i think i would back to named pipes and server/client threads... – Sergey Mar 19 '11 at 18:04
2

You need to implement your own allocator to achieve that. The allocator is a std::vector<> template parameter.

Johann Gerell
  • 24,991
  • 10
  • 72
  • 122
  • This is not really true: there are allocators already made for this purpose, no need to implement it yourself (see my answer regarding Boost Interprocess). Also this answer is not sufficient: as Ben mentioned in another comment, one must be wary of mapping the shared memory at different base addresses in different processes (a pitfall for naive solutions which is difficult to avoid). – John Zwinck Mar 19 '11 at 17:49
  • "there are allocators already made for this purpose, no need to implement it yourself" ... Using anything not already built in to the standard library was implied by _your own_ in my answer. – Johann Gerell Nov 19 '19 at 12:59
1

Use placement new to construct a vector in shared memory. You will also need an allocator for the vector so that it can use shared memory for its element storage. If the vector is just storing int, and you can put the shared memory section at the same virtual address in each process, this just might work.

Adam Mitz
  • 6,025
  • 1
  • 29
  • 28
  • 1
    +1 Placing the shared memory at the same logical address is a necessity for this to work. – Ben Mar 19 '11 at 17:40
  • I think it could work with any object for which you can control the allocator (and in general that doesn't have pointers to non-mmapped memory)... So a std::vector > for example would be ok. – xanatos Mar 19 '11 at 17:40
  • And you can't use objects with vtables (hence the comment about "just storing int"). – Adam Mitz Mar 19 '11 at 17:43
  • As Ben said, this only works if you have a fixed mapping. Which is "not recommended" according to `man mmap` (see MAP_FIXED). You may find yourself (in some process) unable to map the data where it needs to be, in which case this solution will fail. – John Zwinck Mar 19 '11 at 17:45
  • I am storing items of my own class, not ints, and by the way how i can use 'new' here? As i know it allocates from the heap, or i can overload operator new? – Sergey Mar 19 '11 at 17:47
  • Storing your own objects is OK if they don't have vtables and pointers. Please look up "placement new" in your favorite C++ book or google. – Adam Mitz Mar 19 '11 at 17:50
  • @Sergey http://stackoverflow.com/questions/3763846/what-is-an-in-place-constructor-in-c Read of the placement constructor here. You'll use it both for creating the vector and in your allocator to allocate the memory. – xanatos Mar 19 '11 at 17:50
  • @Adam if he can't use objects with vtables (and it's quite clear why), then he can't build the vector in the shared memory. He could build the vector's data in the shared memory, but then how would he attach the vector "from the other side"? – xanatos Mar 19 '11 at 17:52
  • @xantos I don't get the question. The vector can exist in shared memory from placement new. What does attach mean? Certainly some protocol is needed to relay the location of the vector -- that's out of scope for this question. – Adam Mitz Mar 19 '11 at 17:55
  • No, it is really question, by the way how to detect 'real' vector size with all its members. And my object consist of structures, functions and pointers to structures, will this work if i just attach vector pointer to fixed address (if i can calc its size of course)? – Sergey Mar 19 '11 at 18:00
  • @Sergey You will need to carefully control all allocation so that it goes in the shared memory segment. This is a big topic onto itself, maybe open a new SO question and also study how Boost.Interprocess handles it. – Adam Mitz Mar 19 '11 at 18:04
  • @Adam There are two "objects". The vector ontainer" and the "blob" of memory created by the vector using the allocator. The memory of the "vector" container has (hidden) the vtable of the vector (or a vpointer to the vtable). The vtables necessary for a vector from Process1 are different than the vtables necessary for a vector from Process2 (even if they are the same exe... Memory randomization etc.) – xanatos Mar 19 '11 at 18:08
  • vector has a vtable? Since when? – Adam Mitz Mar 19 '11 at 18:09
  • Yeah, `std::vector<>` has no virtual functions hence no vtable of its own. Its contained elements could have vtables, of course, depending on the first template argument. – John Zwinck Mar 19 '11 at 18:11
  • @John is it an immutable promise valid forever that std:vector has no vtable? is it written somewhere? or is it only "all the current implementations don't have a vtable"? – xanatos Mar 19 '11 at 18:14
  • @xanatos the design of the standard containers is contrary to the idea of a vtable. The spec describes exactly what std::vector's interface looks like and it would be very surprising to see a virtual there. StackOverflow comments are not a good way to have this discussion -- perhaps start a new question. – Adam Mitz Mar 19 '11 at 18:16
  • @xanatos: I promise you that `std::vector<>` in C++ will never have a vtable. It's never had one yet, and the C++ standards committee would be exceedingly unlikely to change that. – John Zwinck Mar 19 '11 at 18:20
1

You could use shared memory, mapped at a fixed address, i.e., the address would be the same in each process allowing using raw pointers.

So if you do something like the following, you can have (multiple) regions of shared memory:

#include <boost/interprocess/managed_shared_memory.hpp>
#include <boost/interprocess/allocators/allocator.hpp>

struct MySegment {
    static const size_t alloc_size = 1048576;
    static const void *getAddr() { return (void *)0x400000000LL; }
    static const char *getSegmentName() { return "MySegment"; }
};

template <typename MemorySegment>
class SharedMemory {
public:
    typedef boost::interprocess::fixed_managed_shared_memory shared_memory_t;
    typedef shared_memory_t::segment_manager segment_manager_t;

    static shared_memory_t *getSegment() {
        if (!segment) {
            assert(MemorySegment::getAddr() != 0 && "want a fixed address for all processes");
            segment = new boost::interprocess::managed_shared_memory(
                    boost::interprocess::open_or_create,
                    MemorySegment::getSegmentName(),
                    MemorySegment::alloc_size,
                    MemorySegment::getAddr());
        }
        return segment;
    }
    static segment_manager_t *getSegmentManager() {
        return getSegment()->get_segment_manager(); }

private:
    static boost::interprocess::managed_shared_memory *segment;
};
template <typename MemorySegment>
typename SharedMemory<MemorySegment>::shared_memory_t *SharedMemory<MemorySegment>::segment = NULL;


template <class MemorySegment, class T>
class SharedMemoryAllocator {
public:
    typedef boost::interprocess::allocator<T, typename SharedMemory<MemorySegment>::segment_manager_t> InterprocessAllocator;

    // Delegate all calls to an instance of InterprocessAllocator,
    pointer allocate(size_type n, const void *hint = 0) { return TempAllocator().allocate(n, hint); }
    void deallocate(const pointer &p, size_type n) { return TempAllocator().deallocate(p, n); }
    size_type max_size() const { return TempAllocator().max_size(); }
    void construct(const pointer &ptr, const_reference v) { return TempAllocator().construct(ptr, v); }
    void destroy(const pointer &ptr) { return TempAllocator().destroy(ptr); }

    typedef typename InterprocessAllocator::value_type value_type;
    typedef typename InterprocessAllocator::pointer pointer;
    typedef typename InterprocessAllocator::reference reference;
    typedef typename InterprocessAllocator::const_pointer const_pointer;
    typedef typename InterprocessAllocator::const_reference const_reference;
    typedef typename InterprocessAllocator::size_type size_type;
    typedef typename InterprocessAllocator::difference_type difference_type;

    SharedMemoryAllocator() {}
    template <class U> SharedMemoryAllocator(const SharedMemoryAllocator<MemorySegment, U> &u) {}

    template <typename OtherT> struct rebind { typedef SharedMemoryAllocator<MemorySegment, OtherT> other; };

private:
    static InterprocessAllocator TempAllocator() {
        return InterprocessAllocator(SharedMemory<MemorySegment>::getSegmentManager());
    }
};

Then, you could use:

       std::vector<int, SharedMemoryAllocator<MySegment, int> vec;

and the vector's elements would be put in shared memory (of course, vec will also have to be allocated there).

hrr
  • 1,807
  • 2
  • 21
  • 35