tags:

views:

79

answers:

1

I have a program that I just changed to using a boost::multi_index_container collection. After I did that and tested my code in debug mode, I was feeling pretty good about myself.

However, then I compiled a release build with NDEBUG set, and the code crashed. Not immediately, but sometimes in single-threaded tests and often in multi-threaded tests.

The segmentation faults happen deep inside boost insert and rotate functions related to the index updates and they are happening because a node has NULL left and right pointers.

My code looks a bit like this:

struct Implementation {
    typedef std::pair<uint32_t, uint32_t> update_pair_type;
    struct watch {};
    struct update {};
    typedef boost::multi_index_container<
        update_pair_type,
        boost::multi_index::indexed_by<
            boost::multi_index::ordered_unique<
                boost::multi_index::tag<watch>,
                boost::multi_index::member<update_pair_type, uint32_t, &update_pair_type::first>
            >,   
            boost::multi_index::ordered_non_unique<
                boost::multi_index::tag<update>,
                boost::multi_index::member<update_pair_type, uint32_t, &update_pair_type::second>
            >    
        >    
    > update_map_type;
    typedef std::vector< update_pair_type > update_list_type;

    update_map_type update_map;
    update_map_type::iterator update_hint;

void register_update(uint32_t watch, uint32_t update);
void do_updates(uint32_t start, uint32_t end);
};

void Implementation::register_update(uint32_t watch, uint32_t update)
{
    update_pair_type new_pair( watch_offset, update_offset );
    update_hint = update_map.insert(update_hint, new_pair);
    if( update_hint->second != update_offset ) {
        bool replaced _unused_ = update_map.replace(update_hint, new_pair);
        assert(replaced);
    }
}
+1  A: 

I knew the answer when I posted the question, but I thought I would share this for everyone's edification. I didn't find any answers when I Googled for them myself so I had to figure this out on my own.

I can see other programmers easily falling into the same trap.

The problem is with the update_hint in my code. It is used by the next register_update call. Normally this works really well.

BUT!

Using an insertion hint after calling replace causes that insert hint to be invalid! For some reason it works most of the time and always seems to work in debug mode. Boost seems to use the hint unchecked in some cases in release mode compiles though, and that turns out to be the killer.

Zan Lynx
It's pretty trivial once you take your time and think about it. `replace` is nothing more than syntactic sugar for `erase` + `insert` with possibly some speed-up, thus the iterator is invalidated (since its pointee is deleted). Doing so with a map (`ordered_unique`) would yield the same behavior. A pity that the library does not provide 'checked iterators', it would have come handy during your debug time!
Matthieu M.
@Matthieu: Yes, erase and insert. It is obvious when you consider it. However, because I was replacing code that formerly used a STL map and had directly modified the map value on key collisions, my brain passed completely over the problem.
Zan Lynx