Subtle bugs through leaking
published at 06.10.2016 14:34 by Jens Weller
Save to Instapaper Pocket
Herb Sutters keynote on writing leak free code in C++ reminded me of seeing the opposite as a freelancer: code that has very obvious leaks. And it made me also think about what 'bugs' some of them caused. Most of you will rarely or never encounter such code, as its mostly making its life outside of the C++ mainstream world. But if you are working as a freelancer, you might get to see it sooner or later. I hope that C++11 and the last years have improved things, but in my time as a freelancer from 2007 - 2013, I made a fair share of encounters with such code and philosophies behind it.
Today we have tools helping you to close these leaks, CppCheck, clang tidy and even Visual Studio being some of them. And C++11 ended the time, where manual new and delete should be an option. Previously already many solutions, like boost::shared_ptr and scoped_ptr existed to solve this problem. But, those who write leaky code, usually get to ignore this.
One common theme in seeing code that really is leaky, is that its often written by outsiders, in mobile Apps I often saw this pattern. One such SDK even required you to call Construct() after the object was created with raw new. Asking for smart pointers at one event got me the question, what that would be. Also, there seems to be a fair share of people, thinking that only plain old objects + everything std should be on the stack. Every other object, they use new.
no delete = less crashes
And these people are often smart enough, to figure out, that using delete introduces crashes to your application. Also you risk double delete, which is undefined behavior (yet, this is rarely known to this group). So instead of writing correct, leak and crash free code, they take the much easier approach of simply not using delete. This also avoids the delete vs. delete [] 'mystery'. Which is another, more subtle way to leak memory.
One of the 'benefits' of not using delete is, introducing smart pointers gets easier, as you don't have to erase all delete statements. Yet, calling delete on a smart pointer will cause an compilation error, so that this shouldn't be seen as a big advantage.
Bugs caused
When seeing such a code base, I tried to shut down most or all leaks. Sometimes this was difficult, as it does also introduce fears of introducing new bugs in a perfectly "working" program. An alternative can be to reduce leaking, often code is written with a local mindset. Adding a button here, newing the object locally, every press of the button will create a new leak. Making this a member variable, even with leak, will make n leaks into one, and then wrapping it into a smart pointer, none. Maybe adding an if to see if you need to allocate the resource in the first call.
The number one bug caused by leaks is not crashes, its speed. Every new leak clutters your memory, bloats your access times and fixing most leaks can give visually visible performance gains. Crashes usually occur, when you delete things too early, as mentioned this is often avoided by simply not calling delete. This leads to other errors. One client used a terminal for credit card processing, and every call to the payment function would allocate a new terminal object, leaking it of course. So, after a while, the terminal stopped working. Fix: restart application and terminal. Which btw. is often how users deal with the symptoms of leaky code. Closing this leak made this long standing bug go away.
RAII - no more leaks
Since C++11, even the standard has smart pointers, and except for cyclic usage of shared_ptr, they deal with all leaks. Thanks to RAII - resource acquisition is initialization. RAII objects act as guards to their resources, and usually only have one job to do: free the resource once the guard is destroyed. This extends to mutexes and other resources, for memory it is usually known as the smart pointer concept. This pattern ensures, that every object gets on every code path taken correctly freed.
You might have to implement your own objects, or use a delete handler for unique_ptr if an SDK requires to call certain clean up functions before closing/destruction.
For additional inspiration on how to deal with legacy code, I recommend the simplify C++ blog from Arne Mertz.
Join the Meeting C++ patreon community!
This and other posts on Meeting C++ are enabled by my supporters on patreon!