views:

236

answers:

3

Well, I'm very new to Valgrind and memory leak profilers in general. And I must say it is a bit scary when you start using them cause you can't stop wondering how many leaks you might have left unsolved before!

To the point, as I'm not an experienced in c++ programmer, I would like to check if this is certainly a memory leak or is it that Valgrind is doing a false positive?

typedef std::vector<int> Vector;
typedef std::vector<Vector> VectorVector;
typedef std::map<std::string, Vector*> MapVector;
typedef std::pair<std::string, Vector*> PairVector;
typedef std::map<std::string, Vector*>::iterator IteratorVector;

VectorVector vv;
MapVector m1;
MapVector m2;

vv.push_back(Vector());
m1.insert(PairVector("one", &vv.back()));

vv.push_back(Vector());
m2.insert(PairVector("two", &vv.back()));

IteratorVector i = m1.find("one");
i->second->push_back(10);
m2.insert(PairVector("one", i->second));

m2.clear();
m1.clear();
vv.clear();

Why is that? Shouldn't the clear command call the destructor of every object and every vector?

Now after doing some tests I found different solutions to the leak:

1) Deleting:

i->second->push_back(10);

2) Adding:

delete i->second;

3) Deleting the second

vv.push_back(Vector());
m2.insert(PairVector("two", &vv.back()));

Using solution 2) makes Valgring print: 10 allocs, 11 frees Is that OK?

As I'm not using new why should I delete?

Thanks, for any help!

+1  A: 

You have undefined behaviour here:

m1.insert(PairVector("one", &vv.back()));

vv.push_back(Vector());

Insert invalidates iterators and references pointing into the vector, which means also the pointer you stored in the map is basically pointing to some black hole after the insert.

makes Valgring print: 10 allocs, 11 frees Is that OK?

It's strange, doesn't it also print something about double-frees?

For the solution, I would suggest using a different container than vector (eg. list, or deque, whose mutating functions invalidate iterators, but not references). Or you could store pointers (preferably smart, but could be ordinary) to data in the vector so the adress of actual data is stable.

jpalecek
It does say something about an invalid delete although I don't know how to get rid of the debug calls so I could only see the errors. I will be using Valgrind from now on, so I expect to get a bit more comfortable with it in the next days...
Alberto Toglia
It's not undefined behavior (yet) at the lines you quoted. It's perfectly legal to have an invalid pointer hanging around, *as long as you don't dereference it*. It only becomes undefined on the line `i->second->push_back(10)`, where the vector pointer is actually dereferenced.
jalf
A: 

You are doing some dangerous things with vectors here. You are keeping pointers to vectors that may become invalid during program execution.

std::vector<>::push_back() may invalidate any iterators or references to the std::vector<> if it was already full. Since std::vector<> guarantees that its contents will be stored contiguously (so you can use it in place of an array), when it needs more memory it has to copy itself to a different memory block and the original becomes invalid.

This means that all calls to push_back() in your code (excepting for the first one) lead to undefined behaviour, so anything can be happening here.

Gorpik
@Gorpik, thanks for the tips. But tell me something, The reason I have maps and vectors is that with vectors I warrant that my objects are nicely aligned in memory and the maps are used to find objects with specific names. Obliviously this was not intended to use with ints but large game objects... Does that make sense to you if I'm careful with the stalled pointers?
Alberto Toglia
What a minute now I understand, the vector can move my objects memory to different places on executing when growing, and stuff, making my maps useless. So I think I'm better off using a simple array for this.
Alberto Toglia
@Alberto Toglia: Not really. If you know for sure the size of the vector, you can specify it on construction and it will never move. If you don't, well, the array will go overboard instead of growing and you will have fandango on core.
Gorpik
vector only moves its objects when you resize the vector (which you can't do with an array). If you treat the vector as an array (if it keeps the same size), it's fine, and nothing gets moved. You can call `reserve()` on the vector to specify how much space it should pre-allocate. Then you can insert that many objects with no risk of it having to reallocate and move objects.But really, the problem is the *combination* of vectors and pointers. Pointers to objects stored in vectors are dangerous (at least if the vector is resized)
jalf
@Gorpik, well yeah, I know my array isn't going to grow. But this looks like a common problem, my object memory alignment is a must, and having maps to get these objects's pointers is also a must. How can I have them always updated. I know there's something called handles but I haven't seen a good example similar to this.
Alberto Toglia
@Alberto Toglia: Just use vectors, but specify their size in the constructor (`Vector(100)`, for instance, insted of just `Vector()`) . Then you can be sure that your vectors will not move.
Gorpik
+1  A: 

Basically this line is causing the problem:

i->second->push_back(10);

This is because i->second may have become invalid when you did:

vv.push_back(Vector());

The second time.

There is no need to call clear. When the vv object goes out of scope it will correctly destroy all the objects. Also all the maps don't own any vectors so their destructors do not affect the vectors they point at. Thus your use of clear is not required.

If you want to keep the same overall solution create a list of vectors for your vv object. Then insertion into the list will not affect already existing members and you r maps will work correctly.

std::list<Vector> vv;  // insertion into this will not invalidate any other members.
                       // Thus any pointers to members you have will not become invalidated.

Personally I think you are over complicating things.
I think you can achieve the same results by vastly simplifying this.
If the vectors are not referenced by multiple map elements then just put the vector into the map.

std::map<std::string, std::vector<int> >    m1;

m1["one"].push_back(10);
m1["two"].push_back(20);
Martin York