POV-Ray : Newsgroups : povray.off-topic : Whack a mole : Whack a mole Server Time
28 Jul 2024 14:18:44 EDT (-0400)
  Whack a mole  
From: Orchid Win7 v1
Date: 17 Jan 2014 13:54:44
Message: <52d97c74$1@news.povray.org>
Today, I spent the entire day tracking down memory leaks.

VS is very helpful in that when it runs your C++ code, if at the end of 
the program there's any memory still allocated, it prints out a bunch of 
unintelligible gibberish about it. Stuff like the memory address that's 
allocated, and what data it happens to contain now.

What this does not do, of course, is give you even the vaguest clue 
where the hell in the entire codebase to start looking!

The approach we eventually took - and by "we" I mean my college came up 
with this - is to comment out the entire main program, and progressively 
uncomment it. And it turns out memory leaks can hide in the most 
unexpected places...



The first bug was reasonably easy to flatten. There's a giant global 
variable [Wait! Come back!] that holds a few pointers. One of them gets 
initialised on startup. Guess when it gets released? Exactly...

As it turns out, adding a destructor to the globals class allows me to 
free it. (I wasn't 100% sure that C++ actually runs destructors at 
program exit... but it seems to work.)

Not, of course, that anybody really cares about the 48 bytes of memory 
that was being "leaked". It never increases, and when the program stops 
running *everything* is released anyway. But it shuts VS up.



The second bug was harder. Basically there's some code that creates a 
local variable that's a pointer type, and calls new on it to create an 
object. It then passes it to some system functions who's purpose is not 
entirely clear to me. Nowhere is this object released.

So I inserted a call to release the object immediately after calling the 
system functions. I was fully expecting this to break spectacularly, but 
it has no apparent effect at all. Presumably the system call *copies* 
the data pointed to or something.

(Either that, or the system is now utilising unallocated memory which by 
some miracle doesn't get reused in my test scenario. But I'm reasonably 
sure VS deliberately scribbles over memory when you delete it - or at 
least, it does in debug mode...)

Again, hardly a show-stopper; it's several dozen bytes allocated on 
startup and never released. Well, it's released now. That kept VS quiet 
for a bit.



The next bug took me literally an *entire day* to find. Me and two other 
people spent hours on end pouring over the code trying to decipher what 
the actual **** is going on!

Basically, VS reports a *huge* number of objects not being freed. Some 
of then contain ASCII text which suggests it's something to do with the 
task configuration objects. That's newly-added code; basically we create 
a vector of pointers, populate it, and at the end we iterate over it and 
delete everything it contains.

We disabled everything between populate and clean. So it's not like an 
object is being accidentally dropped somewhere. Again and again we 
inspected the code, commented and uncommented stuff, stepped through it 
in a debugger... we were getting nowhere.

The *only reason* we're even using pointers in the first place is that 
C++ demands that you dangle stuff off the end of a pointer if you want 
any kind of polymorphism at all. And there are 4 sorts of task object, 
which each do something different. You build a task list, give the user 
the option to modify its contents, and then run everything in the list.

Yeah, you guys have already worked this out haven't you? The solution to 
this massive memory leak is a single line of code:

   virtual ~BaseTask() {}

That's it. Just add that to BaseTask.h and suddenly the memory leak 
vanishes.

Basically, it turns out that delete doesn't work properly unless you 
declare a virtual destructor. The default one doesn't do the right 
thing. Without this line of code, delete blindly assumes that the type 
of the pointer specifies the size of the memory chunk to delete. *facepalm*



The next leak was stranger. If I uncomment a particular bit of code, a 
leak appears. But this code doesn't seem to do anything special. 
Certainly it doesn't allocate any memory. So... wuh?

Eventually, I find that there's a class that subclasses wxThread (from 
the wxWidgets library). In particular, running a wxThread causes the 
wxThread::Enter() method to run, and when this method exits, the thread 
stops, and all its resources are freed automatically.

Our implementation for Enter() calls Kill() right at the end. A quick 
glance at the wxWidgets documentation warns you to never, ever do this 
because "the memory allocated to the thread will not be freed". What's 
more, the return statement on the next line exits the method which 
causes the thread to stop *anyway*.

In summary, delete Kill(), and another memory leak vanishes.



All is not well, however. While the actual application runs quite 
happily (as far as I can tell), now when I run the test project, it 
exist with return code -1657845. And on Linux, glibc reports something 
about a double-free. (Then again, the Windows and Linux source code is 
radically different in places...)

I wonder if valgrind would be any help... (But then of course, I'd have 
to figure out how to build the application by hand!)


Post a reply to this message

Copyright 2003-2023 Persistence of Vision Raytracer Pty. Ltd.