POV-Ray : Newsgroups : povray.off-topic : Whack a mole Server Time
28 Jul 2024 16:24:05 EDT (-0400)
  Whack a mole (Message 1 to 6 of 6)  
From: Orchid Win7 v1
Subject: Whack a mole
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

From: Le Forgeron
Subject: Re: Whack a mole
Date: 17 Jan 2014 14:35:38
Message: <52d9860a$1@news.povray.org>
Le 17/01/2014 19:54, Orchid Win7 v1 nous fit lire :
> 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!)

for using valgrind, both at home & work, yes it is useful (but you need
to allow a few options). Very usefull.

valgrind only need a -g (and possibly -O0) to run your program.
Nothing like purify which requires a special compilation.


Post a reply to this message

From: Orchid Win7 v1
Subject: Re: Whack a mole
Date: 17 Jan 2014 14:41:53
Message: <52d98781$1@news.povray.org>
On 17/01/2014 07:35 PM, Le_Forgeron wrote:
> Le 17/01/2014 19:54, Orchid Win7 v1 nous fit lire :
>> 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!)
>
> for using valgrind, both at home&  work, yes it is useful (but you need
> to allow a few options). Very usefull.
>
> valgrind only need a -g (and possibly -O0) to run your program.
> Nothing like purify which requires a special compilation.

The problem is the difficulty of building the program on Linux. Provided 
I can persuade our regular build server to do this for me, it shouldn't 
be too bad.

The rest question is, will I be able to understand what the hell the 
program is trying to tell me? From the documentation I've seen, it looks 
reasonably intelligible. (Not like the raw hex dump that VS gives me...)


Post a reply to this message

From: Le Forgeron
Subject: Re: Whack a mole
Date: 17 Jan 2014 15:09:19
Message: <52d98def$1@news.povray.org>
Le 17/01/2014 20:41, Orchid Win7 v1 nous fit lire :
> On 17/01/2014 07:35 PM, Le_Forgeron wrote:
>> Le 17/01/2014 19:54, Orchid Win7 v1 nous fit lire :
>>> 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!)
>>
>> for using valgrind, both at home&  work, yes it is useful (but you need
>> to allow a few options). Very usefull.
>>
>> valgrind only need a -g (and possibly -O0) to run your program.
>> Nothing like purify which requires a special compilation.
> 
> The problem is the difficulty of building the program on Linux. Provided
> I can persuade our regular build server to do this for me, it shouldn't
> be too bad.
> 
> The rest question is, will I be able to understand what the hell the
> program is trying to tell me? From the documentation I've seen, it looks
> reasonably intelligible. (Not like the raw hex dump that VS gives me...)

If valgrind/memcheck can find the symbols of your code (-g), with option
like -leak-check=full and -track-origin=yes, it will show you where the
leak was allocated (hint: with deep program, you might want to use
--num-callers=100 (default = 12))
The first line is about the allocating line... further, the hierarchy of
the functions up to the top function.

Also fancy: --malloc-fill=A5 --free-fill=8A (or other hex value), to
kill uninitialised variables happily at 0 on normal run.

--log-file is a must have too (--log-file=whateverfile.txt )

The only downside: runtime is multiplied by 50 to 200.
(and multicore/threads get serialised)

Also intersecting: --show-reachable=yes (block referenced by lost blocks...)


Post a reply to this message

From: Warp
Subject: Re: Whack a mole
Date: 18 Jan 2014 13:51:33
Message: <52dacd35@news.povray.org>
Orchid Win7 v1 <voi### [at] devnull> wrote:
> 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!)

valgrind exists precisely for this kind of thing. Last time I checked,
however, it hadn't been ported to Windows, but that was a long time ago,
so things might have changed.

There are other, commercial products that do the same task. However,
given that you are working for a company, it shouldn't be such a big
deal.

I have experience with AQtime, and it worked pretty well. (It also
helps finding performance bottlenecks.) I'm not trying to endorse any
particular commercial product, but it just happens to be the one I have
used.

But then, I seldom need to use any such memory debuggers. 99.99% of such
bugs can be avoided by writing the C++ code properly. I don't even remember
when was the last time I had a memory leak in any C++ program (and I write
C++ constantly as my payjob, and have been doing so for quite a long time.)

-- 
                                                          - Warp


Post a reply to this message

From: Orchid Win7 v1
Subject: Re: Whack a mole
Date: 19 Jan 2014 05:19:57
Message: <52dba6cd@news.povray.org>
>> 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!)
>
> valgrind exists precisely for this kind of thing. Last time I checked,
> however, it hadn't been ported to Windows, but that was a long time ago,
> so things might have changed.

Well, the application does get built for Windows and Linux. (And there 
are some fairly drastic code differences between the two.) It would 
certainly be useful to test the Linux version - and to get something 
more than just a page of hex codes to tell me what the **** is going on. 
Then again, it does mean I have to master building the thing on Linux. 
(Not as simple as clicking "rebuild solution".)

> There are other, commercial products that do the same task. However,
> given that you are working for a company, it shouldn't be such a big
> deal.

Working for a company with very little money. ;-)

> But then, I seldom need to use any such memory debuggers. 99.99% of such
> bugs can be avoided by writing the C++ code properly. I don't even remember
> when was the last time I had a memory leak in any C++ program (and I write
> C++ constantly as my payjob, and have been doing so for quite a long time.)


That's probably the problem. We write C# all day. C++ has drastically 
different semantics, which can trip you up in all sorts of ways.

Look at the post I wrote. Two bugs were due to a conspicuous use of 
"new" for which no corresponding "delete" exists. That's pretty easy to 
recognise, even if fixing it isn't necessarily easy. But the other two 
leaks were due to more unusual causes. In one case, we allocate objects 
and explicitly release them again; the only reason it leaked is because 
(unknown to any of us) you have to explicitly ask for a virtual 
destructor. The compiler doesn't figure that out for you. And the final 
leak was to do with somebody not reading the fine print on an external 
library.

I suppose if you write this kind of code all day, every day, you would 
*know* that destructors need to be virtual and so forth...


Post a reply to this message

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