boostunordered-setboost-interprocess

Boost interprocess throw read access violation


I am using boost's managed_shared_memory, below is the A program which is responsible for creating and constructing an unordered_set.

#include "pch.h"
#include <iostream>
#include <boost/interprocess/managed_shared_memory.hpp>
#include <boost/unordered_set.hpp>
#include <boost/interprocess/allocators/allocator.hpp>
#include<string>

using namespace boost::interprocess;
const char enrtNdbShareSet[] = "enrtTableSet";
struct EnrtRecord
{
    std::string ern_id = "";
    std::string ern_frequency = "";
    std::string ern_name = "";

    bool operator==(const EnrtRecord& other) const {
        return ern_id == other.ern_id && ern_frequency == other.ern_frequency;
    }

    bool IsKeyNotEmpty() const {
        return ern_id != "" && ern_frequency != "";
    }
};
// hash func
struct EnrtRecordHash
{
    size_t operator()(const EnrtRecord& s) const {
        size_t h1 = boost::hash<std::string>()(s.ern_id);
        size_t h2 = boost::hash<std::string>()(s.ern_frequency);
        return h1 ^ (h2 << 1);
    }
};

using EnrtShmemAllocator = boost::interprocess::allocator<EnrtRecord, boost::interprocess::managed_shared_memory::segment_manager>;
using EnrtUnorderedSet = boost::unordered_set<EnrtRecord, EnrtRecordHash, std::equal_to<EnrtRecord>, EnrtShmemAllocator>;

inline std::ostream& operator<<(std::ostream& os, const EnrtRecord& p) {
    os << "enrt_ern_id: " << p.ern_id << ", ern_name: " << p.ern_frequency;
    return os; // 返回ostream对象以支持链式操作  
}

int main()
{
    struct shm_remove
    {
        shm_remove() { shared_memory_object::remove("MySharedMemory"); }
        ~shm_remove() { shared_memory_object::remove("MySharedMemory"); }
    } remover;
    managed_shared_memory segment(create_only, "MySharedMemory", 65536);
    auto mSet = segment.construct<EnrtUnorderedSet>("XX")(3, EnrtRecordHash(), std::equal_to<EnrtRecord>(), segment.get_allocator<EnrtUnorderedSet>());
    EnrtRecord a;
    a.ern_name = "HANGZHOULMM RW25";
    a.ern_id = "AA";
    a.ern_frequency = "444";
    mSet->insert(a);
    while (true) {}
}

the belowing B program is used to read the content in share memory:

#include "pch.h"
#include <iostream>
#include <boost/interprocess/managed_shared_memory.hpp>
#include <boost/unordered_set.hpp>
#include <boost/interprocess/allocators/allocator.hpp>
#include<string>

using namespace boost::interprocess;
const char enrtNdbShareSet[] = "enrtTableSet";
struct EnrtRecord
{
    std::string ern_id = "";
    std::string ern_frequency = "";
    //std::string ern_mag_var = "";
    std::string ern_name = "";
    bool operator==(const EnrtRecord& other) const {
        return ern_id == other.ern_id && ern_frequency == other.ern_frequency;
    }

    bool IsKeyNotEmpty() const {
        return ern_id != "" && ern_frequency != "";
    }
};
// hash func
struct EnrtRecordHash
{
    size_t operator()(const EnrtRecord& s) const {
        size_t h1 = boost::hash<std::string>()(s.ern_id);
        size_t h2 = boost::hash<std::string>()(s.ern_frequency);
        return h1 ^ (h2 << 1);
    }
};

using EnrtShmemAllocator = boost::interprocess::allocator<EnrtRecord, boost::interprocess::managed_shared_memory::segment_manager>;
using EnrtUnorderedSet = boost::unordered_set<EnrtRecord, EnrtRecordHash, std::equal_to<EnrtRecord>, EnrtShmemAllocator>;

inline std::ostream& operator<<(std::ostream& os, const EnrtRecord& p) {
    os << "enrt_ern_id: " << p.ern_id << ", ern_name: " << p.ern_frequency;
    return os; // 返回ostream对象以支持链式操作  
}
int main()
{
    managed_shared_memory segment(open_read_only, "MySharedMemory");


    EnrtUnorderedSet *myGlsset = segment.find<EnrtUnorderedSet>("XX").first;
    for (auto& ele : (*myGlsset)) {
        std::cout << ele << std::endl;
        std::cout<<ele.ern_name << std::endl;
    }
}

but B throw "Exception thrown at 0x75181267 (ucrtbase.dll) in tryINsertSPACE.exe: 0xC0000005: Access violation reading location 0x00BFFEB8." at std::cout<<ele.ern_name << std::endl;.

However when I change A program with a.ern_name = "HANGZHOULMMRW25",it works fine.Lately I tried a.ern_name = "HANGZHOULMMRW250",it throws exception again, it seems related to the length of string instead of the space problem.


Solution

  • Strings are dynamic containers, just like the set. The small size might mask this fact due to Small String Optimization.

    You need to make it use the shared memory allocator to be able to use it. Alternatively, if you can fix the capacity, consider Boost StaticString:

    using String = boost::static_string<128>;
    

    See this modernized version of your program:

    Live On Coliru

    #include <boost/interprocess/allocators/allocator.hpp>
    #include <boost/interprocess/managed_shared_memory.hpp>
    #include <boost/static_string.hpp>
    #include <boost/unordered_set.hpp>
    #include <iostream>
    namespace bip = boost::interprocess;
    
    using String = boost::static_string<128>;
    
    namespace Enr {
        using boost::hash_value; // enabling ADL
        constexpr auto tableName = "enrtTableSet";
    
        struct Record {
            String id, frequency, /*mag_var, */ name;
    
            bool operator==(Record const& other) const = default;
            bool empty() const { return id.empty() && frequency.empty(); }
    
            friend std::ostream& operator<<(std::ostream& os, Record const& rec) {
                return os << "id: " << rec.id                //
                          << " frequency: " << rec.frequency //
                          << " name: " << rec.name;
            }
    
            friend size_t hash_value(Record const& rec) { return hash_value(std::tie(rec.id, rec.frequency)); }
        };
    } // namespace Enr
    
    namespace Shared {
        constexpr auto Name               = "MySharedMemory";
        using Segment                     = bip::managed_shared_memory;
        using Manager                     = Segment::segment_manager;
        template <typename T> using Alloc = bip::allocator<T, Manager>;
    
        template <typename T, typename H = boost::hash<T>, typename P = std::equal_to<T>>
        using Set = boost::unordered_set<T, H, P, Alloc<T>>;
    
        void remove() { Segment::device_type::remove(Name); }
    }; // namespace Shared
    
    using Set = Shared::Set<Enr::Record>;
    
    void programA() {
        Shared::remove();
        Shared::Segment shm(bip::create_only, Shared::Name, 65536);
    
        auto& mSet = *shm.construct<Set>(Enr::tableName)(shm.get_segment_manager());
        mSet.insert(Enr::Record{"AA", "444", "HANGZHOULMM RW25"});
        mSet.insert(Enr::Record{"BB", "555", "HANGZHOULMMRW250 more more more more"});
    }
    
    void programB() {
        Shared::Segment segment(bip::open_read_only, Shared::Name);
    
        Set& myGlsset = *segment.find<Set>(Enr::tableName).first;
        for (auto& ele : myGlsset)
            std::cout << ele << std::endl;
    }
    
    int main(int argc, char**) {
        if (argc <= 1)
            programA();
        else
            programB();
    }
    

    Testing with

    g++ -std=c++20 -O2 -Wall -pedantic -pthread main.cpp -lrt
    ./a.out
    ./a.out B
    

    Prints

    id: AA frequency: 444 name: HANGZHOULMM RW25
    id: BB frequency: 555 name: HANGZHOULMMRW250 more more more more
    

    Actual Dynamic Strings

    They become less convenient because you keep passing allocators around:

    Live On Coliru

    namespace Shared {
        constexpr auto Name               = "MySharedMemory";
        using Segment                     = bip::managed_shared_memory;
        using Manager                     = Segment::segment_manager;
        template <typename T> using Alloc = bip::allocator<T, Manager>;
    
        template <typename T, typename H = boost::hash<T>, typename P = std::equal_to<T>>
        using Set    = boost::unordered_set<T, H, P, Alloc<T>>;
        using String = boost::container::basic_string<char, std::char_traits<char>, Alloc<char>>;
    
        void remove() { Segment::device_type::remove(Name); }
    }; // namespace Shared
    
    namespace Enr {
        using boost::hash_value; // enabling ADL
        constexpr auto tableName = "enrtTableSet";
    
        struct Record {
            using allocator_type = Shared::Alloc<char>; // std::uses_allocator protocol
    
            Record(allocator_type alloc, char const* i, char const* f, char const* n)
                : id(i, alloc)
                , frequency(f, alloc)
                , name(n, alloc) {}
    
            Shared::String id, frequency, /*mag_var, */ name;
    
            bool operator==(Record const& other) const = default;
            bool empty() const { return id.empty() && frequency.empty(); }
    
            friend std::ostream& operator<<(std::ostream& os, Record const& rec) {
                return os << "id: " << rec.id                //
                          << " frequency: " << rec.frequency //
                          << " name: " << rec.name;
            }
    
            friend size_t hash_value(Record const& rec) { return hash_value(std::tie(rec.id, rec.frequency)); }
        };
    } // namespace Enr
    
    using Set = Shared::Set<Enr::Record>;
    
    void programA() {
        Shared::remove();
        Shared::Segment shm(bip::create_only, Shared::Name, 65536);
        auto mgr = shm.get_segment_manager();
    
        auto& mSet = *shm.construct<Set>(Enr::tableName)(mgr);
        mSet.insert(Enr::Record{mgr, "AA", "444", "HANGZHOULMM RW25"});
        mSet.insert(Enr::Record{mgr, "BB", "555", "HANGZHOULMMRW250 more more more more"});
    }
    

    With the same output as before.

    BONUS: Convenience

    Guessing that frequency might actually need to be numeric and using scoped-allocators to alleviate the need to pass around allocators manually:

    using Id        = boost::static_string<16>;
    using Frequency = double;
    using Name      = Shared::String;
    

    Now the advantage of the uses_allocator protocol comes to light:

    mSet.emplace("AA", 444, "HANGZHOULMM RW25");
    mSet.emplace("BB", 555, "HANGZHOULMMRW250 more more more more");
    

    See it Live On Coliru

    #include <boost/container/scoped_allocator.hpp>
    #include <boost/container/string.hpp>
    #include <boost/interprocess/allocators/allocator.hpp>
    #include <boost/interprocess/managed_shared_memory.hpp>
    #include <boost/static_string.hpp>
    #include <boost/unordered_set.hpp>
    #include <iostream>
    namespace bip = boost::interprocess;
    
    namespace Shared {
        namespace bc = boost::container;
    
        constexpr auto ShmName   = "MySharedMemory";
        using Segment                     = bip::managed_shared_memory;
        using Manager                     = Segment::segment_manager;
        template <typename T> using Alloc = bc::scoped_allocator_adaptor<bip::allocator<T, Manager>>;
    
        template <typename T, typename H = boost::hash<T>, typename P = std::equal_to<T>>
        using Set       = boost::unordered_set<T, H, P, Alloc<T>>;
        using String    = bc::basic_string<char, std::char_traits<char>, Alloc<char>>;
    
        void remove() { Segment::device_type::remove(ShmName); }
    }; // namespace Shared
    
    namespace Enr {
        using Id        = boost::static_string<16>;
        using Frequency = double;
        using Name      = Shared::String;
    
        constexpr auto tableName = "enrtTableSet";
        using boost::hash_value; // enabling ADL
    
        struct Record {
            using allocator_type = Name::allocator_type; // std::uses_allocator protocol
    
            Record(char const* i = "", Frequency f = {}, char const* n = "", allocator_type alloc = {})
                : id(i)
                , frequency(f)
                , name(n, alloc) {}
    
            Id        id;
            Frequency frequency;
            Name      name;
    
            bool operator==(Record const& other) const = default;
            bool empty() const { return id.empty() && frequency == Frequency{}; }
    
            friend std::ostream& operator<<(std::ostream& os, Record const& rec) {
                return os << "id: " << rec.id                //
                          << " frequency: " << rec.frequency //
                          << " name: " << rec.name;
            }
    
            friend size_t hash_value(Record const& rec) { return hash_value(std::tie(rec.id, rec.frequency)); }
        };
    } // namespace Enr
    
    using Set = Shared::Set<Enr::Record>;
    
    void programA() {
        Shared::remove();
        Shared::Segment shm(bip::create_only, Shared::ShmName, 65536);
    
        auto& mSet = *shm.construct<Set>(Enr::tableName)(shm.get_segment_manager());
        mSet.emplace("AA", 444, "HANGZHOULMM RW25");
        mSet.emplace("BB", 555, "HANGZHOULMMRW250 more more more more");
    }
    
    void programB() {
        Shared::Segment segment(bip::open_read_only, Shared::ShmName);
    
        Set& myGlsset = *segment.find<Set>(Enr::tableName).first;
        for (auto& ele : myGlsset)
            std::cout << ele << std::endl;
    }
    
    int main(int argc, char**) {
        if (argc <= 1)
            programA();
        else
            programB();
    }
    

    Again with similar output:

    g++ -std=c++20 -O2 -Wall -pedantic -pthread main.cpp -lrt
    ./a.out
    ./a.out B
    id: AA frequency: 444 name: HANGZHOULMM RW25
    id: BB frequency: 555 name: HANGZHOULMMRW250 more more more more