Search code examples
c++debuggingmemorydictionarybad-alloc

std::bad_alloc when inserting into std::unordered_map?


I'm sometimes getting an std::bad_alloc for the following code:

typedef std::unordered_map<chash, block_extended_info> map_type;
map_type m_foo; 

// the transgressor:
auto r = m_foo.insert(map_type::value_type(id, bei));

It happens some of the time while running test cases that use the method containing that line, and other times it doesn't. I had changed the code to use m_foo[id] = bei, which caused it to stop happening for one compilation, but then when I recompiled again it failed, so I changed it to the above, which continued to fail. Clearly the issue is deeper than that.

I am fairly sure it's not a matter of running out of memory since I have top running while the test cases are running and it doesn't get anywhere near filling up the memory.

What may be causing the std::bad_alloc? What in the details of chash and block_extended_info may be causing this? Objects of those types are copied and passed around all over the place in other parts of the code and that doesn't cause any issues.

Here's the definition of chash:

class chash {
  char data[32];
};

And the following is everything required to define block_extended_info:

class csignature {
  chash c, r;
};
enum foob { /* ... */ };
class ct {
  uint64_t a, b;
  foob c;
};
typedef boost::variant< /* structs with ints, maps, vectors of ints */ > txin_v;
typedef boost::variant< /* similar structs */ > txout_target_v;
struct tx_out {
    uint64_t a;
    txout_target_v b;
};
class transaction_prefix
{
public:
  size_t   version;
  uint64_t unlock_time;
  std::vector<uint8_t> extra;

protected:
  std::vector<txin_v> a;
  std::vector<tx_out> b;
  std::vector<ct> c;
  std::vector<ct> d;
};
class transaction: public transaction_prefix
{
public:
  std::vector<std::vector<csignature> > signatures;
};
struct block_header
{
  uint8_t  major_version;
  uint8_t  minor_version;
  uint64_t timestamp;
  chash    prev_id;
  uint32_t nonce;
};
struct block: public block_header
{
  transaction miner_tx;
  std::vector<chash> tx_hashes;
  uint16_t food_id;
  csignature food_sig;
};
struct block_extended_info
{
  block    bl;
  uint64_t height;
  size_t   block_cumulative_size;
  uint64_t cumulative_difficulty;
  uint64_t already_generated_coins;
};

Stepping around the relevant area in gdb, here are the latest few lines that lead up to the bad_alloc:

cryptonote::transaction::transaction(cryptonote::transaction const&) () at /usr/include/c++/4.8/bits/stl_vector.h:313
313       { this->_M_impl._M_finish =
646       { return size_type(this->_M_impl._M_finish - this->_M_impl._M_start); }
91      : _Tp_alloc_type(__a), _M_start(0), _M_finish(0), _M_end_of_storage(0)
168       { return __n != 0 ? _M_impl.allocate(__n) : 0; }
183     this->_M_impl._M_end_of_storage = this->_M_impl._M_start + __n;
181     this->_M_impl._M_start = this->_M_allocate(__n);
182     this->_M_impl._M_finish = this->_M_impl._M_start;
183     this->_M_impl._M_end_of_storage = this->_M_impl._M_start + __n;
310       vector(const vector& __x)
117     __uninit_copy(__first, __last, __result);
cryptonote::tx_out* std::__uninitialized_copy<false>::__uninit_copy<__gnu_cxx::__normal_iterator<cryptonote::tx_out const*, std::vector<cryptonote::tx_out, std::allocator<cryptonote::tx_out> > >, cryptonote::tx_out*>(__gnu_cxx::__normal_iterator<cryptonote::tx_out const*, std::vector<cryptonote::tx_out, std::allocator<cryptonote::tx_out> > >, __gnu_cxx::__normal_iterator<cryptonote::tx_out const*, std::vector<cryptonote::tx_out, std::allocator<cryptonote::tx_out> > >, cryptonote::tx_out*) ()
    at /usr/include/c++/4.8/bits/stl_uninitialized.h:68
68          __uninit_copy(_InputIterator __first, _InputIterator __last,
74            for (; __first != __last; ++__first, ++__cur)
83      }
cryptonote::transaction::transaction(cryptonote::transaction const&) () at /usr/include/c++/4.8/bits/stl_vector.h:313
313       { this->_M_impl._M_finish =
646       { return size_type(this->_M_impl._M_finish - this->_M_impl._M_start); }
91      : _Tp_alloc_type(__a), _M_start(0), _M_finish(0), _M_end_of_storage(0)
646       { return size_type(this->_M_impl._M_finish - this->_M_impl._M_start); }
168       { return __n != 0 ? _M_impl.allocate(__n) : 0; }
101     if (__n > this->max_size())
104     return static_cast<_Tp*>(::operator new(__n * sizeof(_Tp)));
183     this->_M_impl._M_end_of_storage = this->_M_impl._M_start + __n;
181     this->_M_impl._M_start = this->_M_allocate(__n);
182     this->_M_impl._M_finish = this->_M_impl._M_start;
183     this->_M_impl._M_end_of_storage = this->_M_impl._M_start + __n;
310       vector(const vector& __x)
74            for (; __first != __last; ++__first, ++__cur)
71        _ForwardIterator __cur = __result;
75      { ::new(static_cast<void*>(__p)) _T1(std::forward<_Args>(__args)...); }
748     ++_M_current;
74            for (; __first != __last; ++__first, ++__cur)
313       { this->_M_impl._M_finish =
646       { return size_type(this->_M_impl._M_finish - this->_M_impl._M_start); }
91      : _Tp_alloc_type(__a), _M_start(0), _M_finish(0), _M_end_of_storage(0)
646       { return size_type(this->_M_impl._M_finish - this->_M_impl._M_start); }
168       { return __n != 0 ? _M_impl.allocate(__n) : 0; }
183     this->_M_impl._M_end_of_storage = this->_M_impl._M_start + __n;
181     this->_M_impl._M_start = this->_M_allocate(__n);
182     this->_M_impl._M_finish = this->_M_impl._M_start;
183     this->_M_impl._M_end_of_storage = this->_M_impl._M_start + __n;
310       vector(const vector& __x)
74            for (; __first != __last; ++__first, ++__cur)
570   class transaction: public transaction_prefix
313       { this->_M_impl._M_finish =
646       { return size_type(this->_M_impl._M_finish - this->_M_impl._M_start); }
91      : _Tp_alloc_type(__a), _M_start(0), _M_finish(0), _M_end_of_storage(0)
646       { return size_type(this->_M_impl._M_finish - this->_M_impl._M_start); }
168       { return __n != 0 ? _M_impl.allocate(__n) : 0; }
101     if (__n > this->max_size())
104     return static_cast<_Tp*>(::operator new(__n * sizeof(_Tp)));
181     this->_M_impl._M_start = this->_M_allocate(__n);
183     this->_M_impl._M_end_of_storage = this->_M_impl._M_start + __n;
181     this->_M_impl._M_start = this->_M_allocate(__n);
182     this->_M_impl._M_finish = this->_M_impl._M_start;
183     this->_M_impl._M_end_of_storage = this->_M_impl._M_start + __n;
310       vector(const vector& __x)
74            for (; __first != __last; ++__first, ++__cur)
101     if (__n > this->max_size())
75      { ::new(static_cast<void*>(__p)) _T1(std::forward<_Args>(__args)...); }
646       { return size_type(this->_M_impl._M_finish - this->_M_impl._M_start); }
91      : _Tp_alloc_type(__a), _M_start(0), _M_finish(0), _M_end_of_storage(0)
168       { return __n != 0 ? _M_impl.allocate(__n) : 0; }
101     if (__n > this->max_size())
102       std::__throw_bad_alloc();

Solution

  • As πάντα ῥεῖ suspected, the problem was elsewhere.

    The code I listed was in a function called add_block_as_invalid(...). The call site looked something like this:

    // parameter, iterators into m_alternative_chains
    std::list<blocks_ext_by_hash::iterator>& alt_chain; 
    
    for(auto alt_ch_iter = alt_chain.begin(); 
        alt_ch_iter != alt_chain.end(); 
        alt_ch_iter++) 
    {
        // ....
        auto ch_ent = *alt_ch_iter;
        // ....
        m_alternative_chains.erase(ch_ent);
    
        for(auto alt_ch_to_orph_iter = ++alt_ch_iter; 
            alt_ch_to_orph_iter != alt_chain.end(); 
            alt_ch_to_orph_iter++) 
        {
            add_block_as_invalid((*alt_ch_iter)->second, (*alt_ch_iter)->first);
            m_alternative_chains.erase(*alt_ch_to_orph_iter);
        }
    }
    

    The call to add_block_as_invalid kept dereferencing alt_ch_iter, which had already been erased.