user2891462
user2891462

Reputation: 3333

Valgrind reports memory leak in map even when I don't perform dynamic allocations

I've run valgrind and AddressSanitizer on a big project I've made and they both report memory leaks in many maps I use. However, I use no dynamic memory allocation in the program (no calls to new() are made, and no pointers (not even smart pointers) are ever used either, only references).

Unfortunately, the original code is too big and is under a confidentiality clause, so I can't really share it fully here. I have tried to create a minimal reproduction, but I was not successful.

Basically many maps around my code base are supposedly leaking.

Take this code, for example, in which I create a new map whose entries are the average of the values in those same entries of a previous map std::map<WAL_Processed_Key, std::vector<double> > tmp_map:

{
    std::map<WAL_Processed_Key, std::vector<double> > tmp_map
    ...

    std::map<WAL_Processed_Key, double> reduced_map;

    for (auto const& entry : tmp_map)
    {
        reduced_map[entry.first]      // Line 98
            = std::accumulate(
                entry.second.begin(),
                entry.second.end(), 0.0) / entry.second.size();
    }

    return WAL_Map(
            reduced_map, start, end, time_granularity, grid_size);
}

AddressSanitizer complains that:

Direct leak of 112 byte(s) in 1 object(s) allocated from:
    #0 0x7f08f7978532 in operator new(unsigned long) (/usr/lib/x86_64-linux-gnu/libasan.so.2+0x99532)
    #1 0x7f08f6dc47a7 in __gnu_cxx::new_allocator<std::_Rb_tree_node<std::pair<mns::WAL_Map::WAL_Processed_Key const, double> > >::allocate(unsigned long, void const*) (/home/idgc/project-share/mns/common_lib/Debug/libmns.so+0x42d7a7)
    #2 0x7f08f6dc412f in std::allocator_traits<std::allocator<std::_Rb_tree_node<std::pair<mns::WAL_Map::WAL_Processed_Key const, double> > > >::allocate(std::allocator<std::_Rb_tree_node<std::pair<mns::WAL_Map::WAL_Processed_Key const, double> > >&, unsigned long) (/home/idgc/project-share/mns/common_lib/Debug/libmns.so+0x42d12f)
    #3 0x7f08f6dc3574 in std::_Rb_tree<mns::WAL_Map::WAL_Processed_Key, std::pair<mns::WAL_Map::WAL_Processed_Key const, double>, std::_Select1st<std::pair<mns::WAL_Map::WAL_Processed_Key const, double> >, std::less<mns::WAL_Map::WAL_Processed_Key>, std::allocator<std::pair<mns::WAL_Map::WAL_Processed_Key const, double> > >::_M_get_node() (/home/idgc/project-share/mns/common_lib/Debug/libmns.so+0x42c574)
    #4 0x7f08f6dc1c48 in std::_Rb_tree_node<std::pair<mns::WAL_Map::WAL_Processed_Key const, double> >* std::_Rb_tree<mns::WAL_Map::WAL_Processed_Key, std::pair<mns::WAL_Map::WAL_Processed_Key const, double>, std::_Select1st<std::pair<mns::WAL_Map::WAL_Processed_Key const, double> >, std::less<mns::WAL_Map::WAL_Processed_Key>, std::allocator<std::pair<mns::WAL_Map::WAL_Processed_Key const, double> > >::_M_create_node<std::piecewise_construct_t const&, std::tuple<mns::WAL_Map::WAL_Processed_Key const&>, std::tuple<> >(std::piecewise_construct_t const&, std::tuple<mns::WAL_Map::WAL_Processed_Key const&>&&, std::tuple<>&&) (/home/idgc/project-share/mns/common_lib/Debug/libmns.so+0x42ac48)
    #5 0x7f08f6dc005c in std::_Rb_tree_iterator<std::pair<mns::WAL_Map::WAL_Processed_Key const, double> > std::_Rb_tree<mns::WAL_Map::WAL_Processed_Key, std::pair<mns::WAL_Map::WAL_Processed_Key const, double>, std::_Select1st<std::pair<mns::WAL_Map::WAL_Processed_Key const, double> >, std::less<mns::WAL_Map::WAL_Processed_Key>, std::allocator<std::pair<mns::WAL_Map::WAL_Processed_Key const, double> > >::_M_emplace_hint_unique<std::piecewise_construct_t const&, std::tuple<mns::WAL_Map::WAL_Processed_Key const&>, std::tuple<> >(std::_Rb_tree_const_iterator<std::pair<mns::WAL_Map::WAL_Processed_Key const, double> >, std::piecewise_construct_t const&, std::tuple<mns::WAL_Map::WAL_Processed_Key const&>&&, std::tuple<>&&) (/home/idgc/project-share/mns/common_lib/Debug/libmns.so+0x42905c)
    #6 0x7f08f6dbed1a in std::map<mns::WAL_Map::WAL_Processed_Key, double, std::less<mns::WAL_Map::WAL_Processed_Key>, std::allocator<std::pair<mns::WAL_Map::WAL_Processed_Key const, double> > >::operator[](mns::WAL_Map::WAL_Processed_Key const&) /usr/include/c++/5/bits/stl_map.h:483
    #7 0x7f08f6dbd886 in mns::WAL_Map::reduce_map(std::map<mns::WAL_Key, double, std::less<mns::WAL_Key>, std::allocator<std::pair<mns::WAL_Key const, double> > >, double, boost::optional<double>) ../Types/WAL_Map.cpp:98
    #8 0x4ef2f3 in mns::PMS_Algorithm_Manager::process() ../PMS_Algorithm_Manager.cpp:223
    #9 0x54ce7c in main ../main.cpp:262
    #10 0x7f08f5d4c82f in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x2082f)

reduced_map is passed by const reference to construct a new WAL_Map and the goes out of scope, so its destructor should be automatically invoked and the internal memory freed.

Again, there is no dynamic allocation or use of pointers anywhere in the program (outside STL's internals, of course), so I can't really explain the "leaks", particularly as they are reported by two different tools. Only (certain) maps are leaking, I use several sets and vectors around the code and they don't seem to be a problem.

I know valgrind sometimes reports false positives (I'm not sure about AddressSanitizer), and my issue seems similar to this answer. However, export GLIBCXX_FORCE_NEW made no difference in the reports.

So my questions are: can there be memory leaks if no pointers or memory allocations are used (assuming no bugs in STL, which would be suprising)? Is there a way to prove that these are false positives?

Environment:

Upvotes: 3

Views: 1277

Answers (1)

user2891462
user2891462

Reputation: 3333

Solved it! The issue was that the operator< of the map's key did not provide strict weak ordering (see this StackOverflow question for more information).

I had something like this:

struct WAL_Processed_Key
{
    int foo;
    int bar;
    bool operator<(const WAL_Processed_Key& o)
    {
        // Bad implementation, no strict weak ordering!
        return foo < o.foo || bar < o.bar;
    }
};

When I should have had something like this:

struct WAL_Processed_Key
{
    int foo;
    int bar;
    bool operator<(const WAL_Processed_Key& o)
    {
        if (foo < o.foo) return true;
        if (o.foo < foo) return false;
        if (bar < o.bar) return true
        if (o.bar < bar) return false;
        return false;
    }
};

When the comparison operator does not provide strict weak ordering, iterating over the map behaves in unexpected ways (a map can report a map.size() greater than its std::distance(map.begin(), map.end()), for example). My assumption is that the map's destructor cannot access all its internal elements for deletion due to the faulty comparison implementation.

Upvotes: 1

Related Questions