1

Why string stored in variable vectorOfStrings at third place received as "bbbb\xFF\xFF\xFF\xC0" is not presented as value "bbbb" use in push_back method?

Boost version: 1.78

Required system: GNU/linux (any distribution, tested on ubuntu)

Compile command: clang++ main.cpp -g -lrt -pthread -o test (or use g++)

Code:

#include <bits/char_traits.h>
#include <boost/interprocess/allocators/allocator.hpp>
#include <boost/interprocess/containers/string.hpp>
#include <boost/interprocess/containers/vector.hpp>
#include <boost/interprocess/managed_shared_memory.hpp>
#include <cstring>

namespace Interprocess = boost::interprocess;
template <class T>
using ShmAllocator = Interprocess::allocator<T, Interprocess::managed_shared_memory::segment_manager>;
using ShmString = Interprocess::basic_string<char, std::char_traits<char>, ShmAllocator<char>>;

int main()
{
    const auto memoryName = "test_memory";
    Interprocess::shared_memory_object::remove(memoryName);
    auto shm = Interprocess::managed_shared_memory(Interprocess::create_only, memoryName, 1024 * 1024);

    auto stringAllocator = ShmAllocator<ShmString>(shm.get_segment_manager());
    auto intAllocator = ShmAllocator<int>(shm.get_segment_manager());

    auto vectorOfStrings = Interprocess::vector<ShmString, ShmAllocator<ShmString>>(stringAllocator);
    auto vectorOfInts = Interprocess::vector<int, ShmAllocator<int>>(intAllocator);

    {
        const auto z = ShmString("aaaaaaaa", stringAllocator);
        vectorOfStrings.push_back(z);
        vectorOfInts.push_back(7);
    }
    {
        const auto z = ShmString("ccccccccccccccccccccccc", stringAllocator);
        vectorOfStrings.push_back(z);
    }
    {
        const auto z = ShmString("bbbb", stringAllocator);
        vectorOfStrings.push_back(z);
    }

    assert(std::strcmp(vectorOfStrings.at(0).c_str(), "aaaaaaaa") == 0);
    assert(std::strcmp(vectorOfStrings.at(1).c_str(), "ccccccccccccccccccccccc") == 0);
    assert(std::strcmp(vectorOfStrings.at(2).c_str(), "bbbb\xFF\xFF\xFF\xC0") == 0); // value received
    assert(std::strcmp(vectorOfStrings.at(2).c_str(), "bbbb") == 0); // value expected
}
Domirien
  • 11
  • 3

2 Answers2

2

A lot depends on your type declarations. Assuming:

#include <boost/interprocess/containers/string.hpp>
#include <boost/interprocess/containers/vector.hpp>
#include <boost/interprocess/managed_shared_memory.hpp>

namespace Interprocess = boost::interprocess;
using Segment          = Interprocess::managed_shared_memory;
using SMgr             = Segment::segment_manager;
template <typename T>
using ShmAllocator = boost::interprocess::allocator<T, SMgr>;
using ShmString    = Interprocess::basic_string<char, std::char_traits<char>,
                                             ShmAllocator<char>>;
template <typename T>
using ShmVector = Interprocess::vector<T, ShmAllocator<T>>;

I do see your results as well. However, rewriting the asserts like this:

assert(vectorOfStrings.at(0) == "aaaaaaaa");
assert(vectorOfStrings.at(1) == "ccccccccccccccccccccccc");
assert(vectorOfStrings.at(2) == "bbbb");                 // value expected

Makes them just pass. So in short,

assert(vectorOfStrings.at(0) == "aaaaaaaa");
assert(vectorOfStrings.at(1) == "ccccccccccccccccccccccc");
assert(vectorOfStrings.at(2) == "bbbb");

std::string huh = vectorOfStrings.at(2).c_str();
assert(huh == "bbbb"); // huh

std::string fine = vectorOfStrings.at(2);
assert(fine == "bbbb"); // fine

As far as I can tell, this is by design. I cannot find a documented guarantee that NUL-terminator is included in boost::container::basic_string.

That is, even though c_str() (and data()) are specified exactly like the standard library does:

enter image description here

The operator[] and at are NOT: Compare Boost's:

enter image description here

Clearly, it's just illegal to look at operator[size()], whereas the standard library has always contained the guarantee: Does std::string::c_str() always return a null-terminated string?

Returns: *(begin() + pos) if pos < size(), otherwise a reference to an object of type T with value charT()


In short, stick to C++. If you want efficient comparisons just use string_view, std::less<> etc. which Boost fully supports.

Also, I can't help but notice that it is very weird to have the elements of the vectors allocated in shared memory, but not the containers themselves.

Here's my simplified take fixing some of these issues:

Live On Coliru

#undef NDEBUG
#include <boost/container/scoped_allocator.hpp>
#include <boost/interprocess/containers/string.hpp>
#include <boost/interprocess/containers/vector.hpp>
#include <boost/interprocess/managed_shared_memory.hpp>
#include <boost/interprocess/managed_mapped_file.hpp> // for COLIRU
using namespace std::string_view_literals;

namespace bip = boost::interprocess;
namespace Shared {
    //using Segment = bip::managed_shared_memory;
    using Segment = bip::managed_mapped_file; // for COLIRU

    template <typename T>
    using Alloc = boost::container::scoped_allocator_adaptor<
        boost::interprocess::allocator<T, Segment::segment_manager>>;

    using String = bip::basic_string<char, std::char_traits<char>, Alloc<char>>;

    template <typename T> using Vector = bip::vector<T, Alloc<T>>;
} // namespace Shared

int main()
{
    using Strings = Shared::Vector<Shared::String>;
    using Ints    = Shared::Vector<int>;
    auto shm = Shared::Segment(bip::open_or_create, "test_memory", 1024 * 1024);

    auto& strings = *shm.find_or_construct<Strings>("strings")(shm.get_segment_manager());
    auto& ints    = *shm.find_or_construct<Ints>("ints")(shm.get_segment_manager());

    strings.emplace_back("aaaaaaaa");
    ints.emplace_back(7);
    strings.emplace_back("ccccccccccccccccccccccc");
    strings.emplace_back("bbbb");

    assert(strings.at(0) == "aaaaaaaa");
    assert(strings.at(1) == "ccccccccccccccccccccccc");
    assert(strings.at(2) == "bbbb");
}
sehe
  • 374,641
  • 47
  • 450
  • 633
0

Thanks for your answer.

The problem is in null-terminated char

   //! <b>Returns</b>: Returns a pointer to a null-terminated array of characters 
   //!   representing the string's contents. For any string s it is guaranteed 
   //!   that the first s.size() characters in the array pointed to by s.c_str() 
   //!   are equal to the character in s, and that s.c_str()[s.size()] is a null 
   //!   character. Note, however, that it not necessarily the first null character. 
   //!   Characters within a string are permitted to be null. 
   const CharT* c_str() const 
   {  return detail::get_pointer(this->priv_addr()); }

Most importand words:

Note, however, that it not necessarily the first null character

We can not compare array of chars or convert boost::interprocess::string to std::string using .c_str()

We need to use:

assert(std::string{ strings.at(2) } == "bbbb");
Domirien
  • 11
  • 3
  • This explanation makes no sense at all. There is clearly no NUL character inside "bbbb". That comment is confusing, did you get it from `boost::container::basic_string::c_str()`? Because I have actual screenshots from the documentation which should be generated from that very comment and they do not seem to match. I cannot find what you posted in the Boost source code. – sehe Jan 27 '22 at 12:11
  • Besides, [SO] is not a forum, responding to answers should be done using comments. Welcome to [SO]! – sehe Jan 27 '22 at 12:12
  • In fact that comment seems to have never been a part of the Boost Container library since at least 2007 (as `git grep --all -i 'necessarily'` clearly shows). Finally, `std::string { strings.at() }` forces an unnecessary dynamic allocation, instead you can write `strings.at(2) == "bbbb"` which will be much more efficient. – sehe Jan 27 '22 at 12:19
  • I found this comment at old version of boost library: https://www.boost.org/doc/libs/1_47_0/boost/interprocess/containers/container/string.hpp – Domirien Jan 27 '22 at 13:04
  • How old? Older than 2007?! – sehe Jan 27 '22 at 13:04
  • boost 1.47 was released in 2011 – Domirien Jan 27 '22 at 13:17
  • You mean you got it from 1.47.0? Oh wow. Boost Container didn't even exist back then. And `basic_string` was actually part of the Interprocess library. Yeah, citing docs from that version is... probably not very helpful. (I had just looked at the raw git history for the Boost Container library, which does show a version from 2007 coming from SVN.) – sehe Jan 27 '22 at 13:21
  • Yeah, after 1.47 version this comments disappears, why? I don't know, but this rule is actual up to the latest version. – Domirien Jan 27 '22 at 13:34
  • The whole file doesn't exist anymore. Boost Container became it's own library. "This rule"? What rule? And why do you think it is actual, since the documentation doesn't state or, even implying the opposite? – sehe Jan 27 '22 at 14:44