In my 21 year career, I've met a lot of coders. Getting to know a new coder is always an entertaining and informative experience. Unfortunately, there's a common belief I've encountered among many coders that makes me sad in my pants: the belief that memory management is hard!
So many coders seemingly have the idea that memory management is a task to be handled by geniuses or the heroes of ancient Greece. And, looking at some of the ridiculous memory leaks I've seen in production systems makes me want to agree with them. I mean, if so many coders get it wrong, then memory management MUST be hard, right?
Wrong! In fact, memory management is one of the easiest tasks a coder can perform. To manage your memory flawlessly like I do, follow these simple steps:
- When you need it, allocate it.
- When you're done with it, free it.
See? That's not so hard! All the problems with memory management stem from not knowing when to free objects. If you don't know when to allocate your objects then you might want to get a knife and stab yourself because you have no business writing code; suicide is a valid option.
This indecision or lack of clarity about object lifetimes brings coders to invent clever schemes to automate the process. The worst scheme yet invented by man is the use of reference counted objects with smart pointers. If you use this scheme in your code, then you are propagating evil and must be stopped!
If you don't know about this scheme then I hesitate to describe it lest you fall into the trap of thinking it's a good idea. But, for sake of completion, I'll make the full horror of this abomination known! The basic idea goes like this:
- Create a base class for all your objects (let's call it RefObj). This base class holds a reference count integer that tracks how many things are referencing it. When that count gets to zero, the object is freed.
- Create a smart pointer class that auto automatically increments and decrements the reference count of RefObj instances.
On the surface, this seems like a good idea. Properly implemented, this system can automate your memory management. What could be better? But, like many so-called good ideas, this one is from the Devil! Let me tell you why:
Smart Pointers Are Dumb
Here's the thing with "smart pointers." They're dumb. Why?
- They pretend to be something that they're not. Smart pointers are not pointers. A pointer is a very lightweight concept. It simply points. Smart pointers, on the other hand, do all kinds of implicit work. This makes them decidedly heavier than pointers. Yet, syntactically, they look just like pointers. Eww.
- They introduce "Fat Man Syndrome" to your code. Each time a smart pointer is passed along, there's implicit overhead. When you pass them by value, you have the overhead of reference count management. And, when you pass them by reference, there's still that extra deference taking place all over your code. Like looking for the single bulge on a fat man, this implicit overhead slows your app down in a hard to pinpoint fashion.
Reference Cycles Kill You
This scheme doesn't handle reference cycles at all. Imagine two RefObj instances that have smart pointers to each other. Each one has increased the reference count of the other. Because of this, neither one will get freed. This is a real problem... not unlike two lovers holding hands while jumping into the Grand Canyon.
Clever coders solve this problem by making a new kind of smart pointer; the weak reference. This smart pointer doesn't increment the reference count of its target. Instead, it registers for a callback when the target is deleted and sets itself to null.
Great, now we have two kinds of smart pointers that are effectively interchangeable. And, the extra complexity of having to know when to use each one. Sounds like a recipe for subtle errors and madness to me. Not to mention the additional overhead of tracking those weak references.
Finding Leaks Is A Bitch
The well-intentioned coder that implements this scheme sets out to stop memory leaks and get his memory management under control. But, regardless, leaks will happen. Ironically, this scheme makes finding the source of memory leaks incredibly difficult. This is because when an object is supposed to become invalidated is unknown. All the coder knows at the time of a leak is that the reference count of the object is not zero. What code incremented the count? Who can say?
This can best be illustrated with a real-world example.
Several years ago, I worked at a company called Ubisoft / Wolfpack. I was hired on to help create a prototype for an unannounced MMO project. Well, this company also had a little game called Shadowbane. While we were ramping up on the design for the prototype, I was tasked with cleaning up that game. Now, the problem with Shadowbane was that its main architect had no idea how to manage memory. The game server was leaking megabytes per hour. It needed daily restarts because of this.
How did they manage memory? Through reference counted objects and smart pointers. Yet, the leaks in that code were intolerably bad. So, here I am, a new hire looking at this tremendous code base and trying to figure out why it leaked so bad. What did I have to do to get this beast tamed?
- I implemented an object tracking system that produced useful logs and snapshots of memory in use. This let me see what was leaking.
- I implemented a reference tracking system that captured the call stack for each reference held to an object. This allowed me to determine why something was leaking.
- I added the weak reference concept to the code base. Yeah, it wasn't in there already!
- I implemented a custom C++ static analysis program based on an open-source library to process the code and find usage errors for the smart pointers and reference counts.
After about a month of work, I had it well in hand. But not until I had written a large number of custom tools and added painfully slow debugging processes to the code. It literally took hours to perform a single iteration of callstack snapshot reference debugging. That is, of course, why I created the static analysis program.
This is the result of such a scheme implemented in a large project!
I hope it's clear to you that reference counted objects and smart pointers are the Devil's work. If you use them, you must be retarded (or a glutton for punishment). Thankfully, there are some simple alternatives.
Understand Your Lifetimes
The easiest way to manage your memory is to thoroughly understand your object lifetimes. Whenever you create an object, you should already know when it should be destroyed. Simple rule: if you make it, make sure you destroy it. You'd think this would go without saying...
One of the problems with freeing an object is that you may have done so while other things are still pointing at it. This introduces dangling pointers and crashes! To combat the evil of dangling pointers, I like to use a basic "watcher" system. This is not unlike the weak reference discussed above. The implementation is like this:
- Create a base class "Watchable" that you derive from on objects that should broadcast when they're being deleted. The Watchable object keeps track of other objects pointing at it.
- Create a "Watcher" smart pointer that, when assigned to, adds itself to the list of objects to be informed when its target goes away.
This basic technique will go a long way toward solving dangling pointer issues without sacrificing explicit control over when an object is to be destroyed.
Use Decent Tools
If you're coding on Windows, take a look at "Glowcode." It is, hands down, the best profiler / memory leak detector on the market. It's awesome!
Use Garbage Collection
There are actually some C/C++ garbage collection libraries out there: check this out. They can be usefully applied, although retrofitting them to an existing app would be quite a challenge.
My personal preference is to couple my game engines with Lua. Keeping the game logic in Lua allows me to take advantage of the garbage collection support provided by that language.
Don't be a dick and use reference counting smart pointers in your code. There's a special place in hell for retards that do that. Just don't!