Fixing Memory Leaks in KDE
Harri Porten
Update: with the advent of Julian Seward's Valgrind tool (see
link below) the cumbersome techniques described in the latter part of
this tutorial 3 years ago have mostly become a thing of the past. Luckily.
Overview
This document aims at programmers and testers wishing to hunt for memory
leaks in theirs or other's applications. A lot of testing and debugging
has to invested to keep up with the stability of the underlying UNIX operating
systems so the following could be of some help.
After a few words on the theory it mainly concentrates on a simple
tool that called LeakTracer to demonstrate how to get going. I'll
happily accept any kind of contribution that will help to correct and complete
this paper.
What is a memory leak ?
A memory leak is a part of memory that has been allocated but never got
freed after its use. The more often this occurs the more valuable memory
will be wasted and taken away from other processes. In the worst case your
application's memory usage will exceed the physical memory size and finally
crash the system when the limit of virtual memory is reached after a period
of heavy hard disc activity.
Will I ever get my memory back ?
Yes. The kernel will completely free all memory allocated by a process
when the process terminates (exception: shared memory). That's why a memory
leak isn't necessary evil. If the size of the leak is guaranteed to be
fixed during the whole lifetime of the program the negative impact is limited
and might be acceptable. If the leak is growable, though, you'll surely
have users that decide to run your application long enough to make it crash.
An Example
Let's take a look at a simple example:
leak.cpp
| #include <stdio.h>
int main(int argc, char **argv)
{
char *p = new char[10];
p[0] = 'A';
printf("%c\n", p[0]);
} |
This program allocates some memory to store an array of 10 characters but
never explicitly calls 'delete [] p;' to free this memory. This
leak certainly can be categorized as harmless. main() will be
run only once, thus the size of the leak is fixed and the memory will be
freed after the execution.
But beware: One day you (or somebody else) may want to reuse your code
and paste it into a method, though. The built-in leak might have already
been forgotten and will have the potential to cause you some headache if
that method is called often enough.
How to detect leaks ?
The most obvious way would be to intensively stare at your code for a long
time to find out whether every new operator has a corresponding
delete
(new[]/delete [] for arrays or malloc()/free()
in plain C). Take care not to interchange these operators ! The behaviour
of delete[] on new'ed data or vice versa is undefined.
This inspection should definitely be your first step but the more complex
your code gets the more likely you'll oversee something.
Alternatively you could do some heavy testing with your program and
watch its memory consumption with ps.
There are a few tools out there that will help you to track down a
leak. The most prominent one is 'Purify'. It's a quite powerful tool that
let's you detect all sorts of errors but it is quite expensive, too. I
came across LeakTracer which is free tool and turned out to be quite
usable for my needs. Alternatively you might check out
LeakTracer
LeakTracer is written by Erwin Andreasen and can be obtained from
http://www.andreasen.org/LeakTracer/
(~10kB). It is not capable of detecting errors like accessing memory
beyond an array's bound or accessing memory that has already been freed.
All it does is to overload the new and delete operators
and do some bookkeeping on their usage. Therfore, it's meant for C++ programs
only. After a program is being run LeakTracer
will output a table containing potential memory leaks together with the
address of the originating call. A perl script named leak-analyze
will help you to interpret this output by matching these addresses to the
respective lines of code via gdb.
Download LeakTracer.tar.gz and un-tar it wherever you
want. The included README gives detailed instructions on how to use but
I'll give you a quick guide here, too. For easy usage I installed the leak-analyze
script in /usr/local/bin/ and adapted the first line to reflect
the location of perl on my system. The pre-compiled LeakTracer.*
files
should simply be copied into the source directory of the application to
be debugged. To prepare the tracing you have to compile your sources with
the -g debugging option and link them with LeakTracer.o.
To ensure proper operation the latter one should be the last one
in the list of object files. Now, simply run your app, do some testing,
close it and run leak-analyze
to prepare a report and view the
results. Let's exercise this with
leak.cpp from above:
>g++ -c -g leak.cpp
>g++ leak.o LeakTracer.o
>./a.out
A
> |
The raw tracing output has been saved in leak.out. Let's convert
it into a human readable form:
>leak-analyze a.out
Gathered 1 (1 unique) points of data.
(gdb)
Allocations: 1 / Size: 10
0x804882d is in main (leak.cpp:5).
4
5 char *p
= new char[10];
> |
Interpretation: there has been one memory allocation triggered in line
5 of leak.cpp that totaled to 10 bytes being reserved. At this
point you can start fixing your code and repeat the whole procedure to
verify the result. Be very careful when applying the fix. Freeing
the memory too early, i.e. when another part of the program still relies
on it is a nasty bug that might be difficult to track down later.
A word about delete: it is good habit to assign NULL to a pointer
after the object it has been pointing to is deleted. This way a second
delete
on this pointer won't hurt at all. Otherwise you'll most likely get a segmentation
fault. LeakTracer will catch this error and abort after printing
a "delete on an already deleted value?" message.
Due to the object inheritance it can be quite tricky to understand when
exactly a certain part of the code (constructor, destructors) is executed.
This is were a debugger becomes quite useful. Your executable should already
contain the relevant debugging info. If LeakTracers internal functions
get into you way you should recompile omitting LeakTracer.o.
I suggest setting a breakpoint at the relevant new to quickly
determine when it is called.
Analyzing Qt or KDE Applications
This is bit more complicated. You better do not touch your working copies
of the libraries and applications and built the experimental versions in
a dedicated directory instead. First of all recompile the Qt library as
debug
version.
You may also chose static. As of LeakTracer 1.4 this
is not really required anymore and results in 19MB libqt but it makes debugging
a lot easier.
Next I installed the KDE libraries into /usr/local/kde-debug. I
saw no need to give kdesupport a special treatment but
configured kdelibs with --enable-debug. Here to you may
also add --enable-static if you don't want to be bothered with
the overhead of dynamically loaded code if you plan to use a debugger.
At this point I'd recommend not to immediately try to analyze a full
blown application yet but to start out with a simple application. The following
should suffice to get you going:
simple.cpp
| #include <kapp.h>
int main(int argc, char **argv)
{
KApplication k(argc, argv);
} |
Copy LeakTracer.o (and LeakTracer.cc for debugging)
to your working directory.
>g++ -g -c simple.cpp -I/usr/local/kde-debug/include -I/usr/local/qt-debug/include
>g++ -g -static simple.o /usr/local/kde-debug/lib/libkdecore.a
/usr/local/qt-debug/lib/libqt.a /usr/X11R6/lib/libXext.a /usr/X11R6/lib/libX11.a
LeakTracer.o |
This will compile our sample as completely static (try ldd a.out
to verify) leading to a 7.7 MB binary. Now, run ./a.out
and view the result with
leak-analyze a.out. If you have not chosen
to use static libraries you can ask leak-analyze to let gdb load
the libraries by setting a breakpoint. Example: leak-analyze a.out
leak.out main.
My first attempts to instrument a real KDE app statically showed
that passing
--enable-debug and --enable-static to configure
wasn't
enough sometimes. After adding LeakTracer.o to the OBJECT list in
Makefile I had to manually insert the static variants of system libraries
(see example above) and add the --all-static and/or
-static
option.
A point worth mentioning when dealing with QWidgets and derived Qt and
KDE subclasses: The destructor of a parent object destroys all child objects.
This saves you from writing your own clean-up code for widgets that have
been constructed with a pointer to the parent.
The following example taken from Qt Tutorial 4 shows how to attach a
button to its parent. The Qt library will take care of the deleting quit
as soon as MyWidget gets destroyed.
MyWidget::MyWidget(QWidget *parent, const char *name)
: QWidget(parent, name)
{
...
QPushButton *quit = new QPushButton("Quit", this, "quit");
...
} |
Thanks
I would like to thank the following people:
Erwin Andreasen for writing LeakTracer
Leon Widdershoven for suggesting clarifications/additions.
Mark Calder for the pointer to memprof
Last modified on July 15 2002. Please mail me any comments, corrections
or suggestions. Thanks. Harri
Porten