Find memory leaks caused by smart pointers
Asked Answered
G

12

20

Does anybody know a "technique" to discover memory leaks caused by smart pointers? I am currently working on a large project written in C++ that heavily uses smart pointers with reference counting. Obviously we have some memory leaks caused by smart pointers, that are still referenced somewhere in the code, so that their memory does not get free'd. It's very hard to find the line of code with the "needless" reference, that causes the corresponding object not to be free'd (although it's not of use any longer).

I found some advice in the web, that proposed to collect call stacks of the increment/decrement operations of the reference counter. This gives me a good hint, which piece of code has caused the reference counter to get increased or decreased.

But what I need is some kind of algorithm that groups the corresponding "increase/decrease call stacks" together. After removing these pairs of call stacks, I hopefully have (at least) one "increase call stack" left over, that shows me the piece of code with the "needless" reference, that caused the corresponding object not to be freed. Now it will be no big deal to fix the leak!

But has anybody an idea for an "algorithm" that does the grouping?

Development takes place under Windows XP.

(I hope someone understood, what I tried to explain ...)

EDIt: I am talking about leaks caused by circular references.

Gent answered 15/9, 2008 at 21:29 Comment(3)
Not sure I understand... can you reference a programming language or platform more specifically? Memory leaks can vary in their handling.Howbeit
Smart pointers generally mean C++/STL.Yoon
There's some confusion here. I think it would be worth distinguishing between allocated memory that doesn't have a pointer (shouldn't happen with smart pointers), leaks caused by circular references, and objects whose life time is extended needlessly because the pointer is kept around.Sesqui
T
19

Note that one source of leaks with reference-counting smart pointers are pointers with circular dependancies. For example, A have a smart pointer to B, and B have a smart pointer to A. Neither A nor B will be destroyed. You will have to find, and then break the dependancies.

If possible, use boost smart pointers, and use shared_ptr for pointers which are supposed to be owners of the data, and weak_ptr for pointers not supposed to call delete.

Tondatone answered 17/9, 2008 at 0:9 Comment(0)
D
7

The way I do it is simply: - on every AddRef() record call-stack, - matching Release() removes it. This way at the end of the program I'm left with AddRefs() without maching Releases. No need to match pairs,

Deena answered 15/9, 2008 at 21:45 Comment(0)
B
4

If you can reproduce the leak in a deterministic way, a simple technique I often used is to number all your smart pointers in their order of construction (use a static counter in the constructor), and report this ID together with the leak. Then run the program again, and trigger a DebugBreak() when the smart pointer with the same ID gets constructed.

You should also consider this great tool : http://www.codeproject.com/KB/applications/visualleakdetector.aspx

Byzantium answered 16/9, 2008 at 14:27 Comment(1)
You could do this in along with the stack dump. This would allow you to pair the outputs via the UID of the refs/derefsLombroso
W
4

What I do is wrap the smart pointer with a class that takes FUNCTION and LINE parameters. Increment a count for that function and line every time the constructor is called, and decrement the count every time the destructor is called. then, write a function that dumps the function/line/count information. That tells you where all of your references were created

Wondrous answered 16/9, 2008 at 14:45 Comment(4)
I've used this technique. If everything ends up coming from CreateObj, then pass the caller of CreateObj's FUNCTION/LINE. (Of course, doing this via macros. Even better if you have a function "Who Called Me?")Ewen
@Krazy/Joe: Can you please point me to some example which shows how to find "Who called me?"Proportionate
Identifying the caller is just the first part of a stacktrace, so #77505 gives you what you need. Backtrace() / execinfo.h.Ewen
In my current project I was too lazy to do execinfo (actually, my coworkers did not want to add the stacktrace code), so I just forked gdb, the Gnu Debugger, and used gdb to provide the caller info. (Warning: I learned that there are bugs in pthreads that led to hangs in forks that should not have happened per POSIX.) At least in the old and crufty version of Linux we are using. // Although I would not use GDB for anything I wanted to be fast.Ewen
D
4

To detect reference cycles you need to have a graph of all reference-counted objects. Such a graph is not easy to construct, but it can be done.

Create a global set<CRefCounted*> to register living reference-counted objects. This is easier if you have common AddRef() implementation - just add this pointer to the set when object's reference count goes from 0 to 1. Similarly, in Release() remove object from the set when it's reference count goes from 1 to 0.

Next, provide some way to get the set of referenced objects from each CRefCounted*. It could be a virtual set<CRefCounted*> CRefCounted::get_children() or whatever suits you. Now you have a way to walk the graph.

Finally, implement your favorite algorithm for cycle detection in a directed graph. Start the program, create some cycles and run cycle detector. Enjoy! :)

Dual answered 9/10, 2008 at 21:44 Comment(2)
I like your way of thinking, but I don't think there is an easy way to get referenced objects from an object: get_childrenMelody
@lzprgmr, Yes, get_children() has to be coded for each participating class manually with the knowledge of references the class maintains. That's what I mean by "not easy to construct".Dual
O
2

What I have done to solve this is to override the malloc/new & free/delete operators such that they keep track in a data structure as much as possible about the operation you are performing.

For example, when overriding malloc/new, You can create a record of the caller's address, the amount of bytes requested, the assigned pointer value returned and a sequence ID so all your records can be sequenced (I do not know if you deal with threads but you need to take that into account, too).

When writing the free/delete routines, I also keep track of the caller's address and the pointer info. Then I look backwards into the list and try to match the malloc/new counterpart using the pointer as my key. If I don't find it, raise a red flag.

If you can afford it, you can embed in your data the sequence ID to be absolutely sure who and when allocation call was made. The key here is to uniquely identify each transaction pair as much as we can.

Then you will have a third routine displaying your memory allocations/deallocation history, along with the functions invoking each transaction. (this can be accomplished by parsing the symbolic map out of your linker). You will know how much memory you will have allocated at any time and who did it.

If you don't have enough resources to perform these transactions (my typical case for 8-bit microcontrollers), you can output the same information via a serial or TCP link to another machine with enough resources.

Oscilloscope answered 15/9, 2008 at 21:53 Comment(0)
W
2

Since you said that you're using Windows, you may be able to take advantage of Microsoft's user-mode dump heap utility, UMDH, which comes with the Debugging Tools for Windows. UMDH makes snapshots of your application's memory usage, recording the stack used for each allocation, and lets you compare multiple snapshots to see which calls to the allocator "leaked" memory. It also translates the stack traces to symbols for you using dbghelp.dll.

There's also another Microsoft tool called "LeakDiag" that supports more memory allocators than UMDH, but it's a bit more difficult to find and doesn't seem to be actively maintained. The latest version is at least five years old, if I recall correctly.

Wynn answered 16/9, 2008 at 2:21 Comment(0)
D
2

It's not a matter of finding a leak. In case of smart-pointers it'll most probably direct to some generic place like CreateObject(), which is being called thousands of time. It's a matter of determining what place in the code didnt call Release() on ref-counted object.

Deena answered 16/9, 2008 at 7:24 Comment(0)
P
1

If I were you I would take the log and write a quick script to do something like the following (mine is in Ruby):

def allocation?(line)
  # determine if this line is a log line indicating allocation/deallocation
end

def unique_stack(line)
  # return a string that is equal for pairs of allocation/deallocation
end

allocations = []
file = File.new "the-log.log"
file.each_line { |line|
  # custom function to determine if line is an alloc/dealloc
  if allocation? line
    # custom function to get unique stack trace where the return value
    # is the same for a alloc and dealloc
    allocations[allocations.length] = unique_stack line
  end
}

allocations.sort!

# go through and remove pairs of allocations that equal,
# ideally 1 will be remaining....
index = 0

while index < allocations.size - 1
  if allocations[index] == allocations[index + 1]
    allocations.delete_at index
  else
    index = index + 1
  end
end

allocations.each { |line|
  puts line
}

This basically goes through the log and captures each allocation/deallocation and stores a unique value for each pair, then sort it and remove pairs that match, see what's left.

Update: Sorry for all the intermediary edits (I accidentally posted before I was done)

Pastelist answered 15/9, 2008 at 21:37 Comment(1)
Note, I assumed you already have a log with the alloc/free traces you talk about in your question, but the other answers may better help you solve the memory leaks in generalPastelist
T
1

For Windows, check out:

MFC Memory Leak Detection

Telephonist answered 15/9, 2008 at 22:26 Comment(0)
P
1

I am a big fan of Google's Heapchecker -- it will not catch all leaks, but it gets most of them. (Tip: Link it into all your unittests.)

Pedanticism answered 16/9, 2008 at 9:21 Comment(0)
B
0

First step could be to know what class is leaking. Once you know it, you can find who is increasing the reference: 1. put a breakpoint on the constructor of class that is wrapped by shared_ptr. 2. step in with debugger inside shared_ptr when its increasing the reference count: look at variable pn->pi_->use_count_ Take the address of that variable by evaluating expression (something like this: &this->pn->pi_.use_count_), you will get an address 3. In visual studio debugger, go to Debug->New Breakpoint->New Data Breakpoint... Enter the address of the variable 4. Run the program. Your program will stop every time when some point in the code is increasing and decreasing the reference counter. Then you need to check if those are matching.

Bhakti answered 21/3, 2013 at 11:51 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.