Rapid Learning: Secret Weapon of the Successful Technologist. |
NOTE
This page was written in 1997, and was tech edited with Borland Turbo C++. Much of the code is not appropriate, or in some cases even accurate, for modern compilers such as GNU gcc. Nevertheless, the ideas expressed on this page are as essential now as they were in 1997. Memory leak is a scourge that must be vanquished, and doing so is not impossible, but is simply a matter of a systematic approach to design and debugging. |
To me, letting code with memory leaks out the door is the mark of an
amateur. Some disagree with me. For those who agree with me, I offer
"how
to eliminate memory leak". To serve those who disagree with me, I offer
"The Top Ten Excuses for Memory Leak". Enjoy!
No, wrong. Leaky programs tend to display wierd intermittents ten times harder to troubleshoot than a reproducible error. It will drive the users nuts.
Of course, a good Troubleshooter can trace the memory leak down to the line of code. And if they find it in your code, everyone will hate you. Not to mention that your transgression might be published on the web...
Don't write apps exceeding your abilities as a programmer. Instead, craft your app from a collection of encapsulated and independent classes, each of which can be tested and de-leaked independently.
Users won't forgive your bugs because of your choice of application framework any more than you'd forgive your housepainters sloppy work because he chose a bad sprayer. Gone are the days when your only choices were MFC, OWL, and DOITYOURSELF. Borland's C++ Builder is based on Ultra-Modular VCL. Powersoft's C++ product also shows promise.
If you're afraid to switch application frameworks because you want the one made in Redmond, beware. They won't blame the guys in Redmond for your bugs, they'll blame you. And a competitor with solid code will eat your lunch.
Leaks in the OS? Maybe that's true and maybe it isn't. The only way to find out is a complete analysis of your program and classes. And that's only possible with a modularly crafted app. If you do prove a leak is in the OS, it's your job as a programmer to work around it.
You can demand better of your third party vendors. Demand that their offerings come in distinct, modular, and independent classes. Then test each class for memory leak. Don't have the time? Then pay the price in the marketplace.
OK, fine. Kick the product out the door in four months, with memory leaks (and the resulting other problems). Now start your next 4 month production cycle. With huge volumes of trouble calls yanking your programmers away from their work. And trade magazines demanding explanations for the bugs. And decreased revenue from the tarnished reputation.
If you have problems paying for good talent now, imagine how tough it will be when your revenue stream dries up from buggy product. Give your good programmers large raises commensurate with their willingness to accept training in modular and independent class construction and use. Those large raises are nothing next to what you'll have to pay a new guy.
Read the trades. Today's corporate managers are sick and tired of the high cost of ownership, and are investigating possible solutions. One of those proposed solutions, the Java enabled Network PC, would put a lot of people, including our favorite operating system, out of business. Many lament the fact that in many industries, all this automation has brought almost no increase in productivity. Our economy can't continue to support buggy software. Changes are coming. And when they do, memory leaks will not be forgiven.
! WARNING !
This section worked with Borland Turbo C 3.0. It does not work for modern C++ implementations such as GCC, and it also bad C++. This section is over 7 years old, but it's kept for those few who still must maintain Turbo C code. |
#define zap(x) if(x){delete(x); x=0;}Then, assuming all pointers start their lives as null pointers, I can safely delete what's been newed, and not delete things that haven't been newed, with a series of zap() commands like this:
zap(pchFirstname);There is nothing magical about this, it just saves repetative code and makes it more readable. Do not stick a typecast in the zap() command -- if something errors out on the above zap() command it likely has another error somewhere.
zap(pchLastname);
zap(pchJobDescription);
One more note. Stroustrup says in his C++ book that #define macros are obsolete in C++. For the most part I agree with him, but this macro really is just a substitute for lines of code, so I think it's the best way to do it.
I like to make sure the object was really allocated, so I make sure the pointers start their lives as null. Then I make deallocate() a series of zap() commands. My deallocate() method then contains code like:
zap(pchFirstname);Of course, my destructor will call deallocate(), as well as any ABEND method.
zap(pchLastname);
zap(pmyobject);
void MYCLASS::makeFilename(const char *szName, const char *szDir)The if the method above is called 1000 times in a loop, you'll end up with one valid char pointer and 999 disembodied memory chuncks. The trick is to put the following line of code just before the new command:
{
pchMemberDir = new char[strlen(szName]+strlen(szDir)+2]; //strlens plus backslash plus null
//*** DO THE STRCPY() AND STRCAT() COMMANDS HERE ***
}
zap(pchMemberDir) //if exists from past call, delete it
NOTE
The following code uses the old Borland Turbo C coreleft() function. There is no direct equivalent in GNU GCC. As a Linux substitute, I often run the code to test within a loop executing it hundreds or thousands of times, or occasionally even millions, while watching the system memory in a top or vmstat -a 1 command. If memory significantly decreases over time, there's a leak. |
void testMyclassOnce()
{
MYCLASS myobject;
//*** EXERCISE ALL METHODS BELOW ***
//*** COMMENT IN THE NEXT AS LEAKS ARE ELIMINATED FROM PREVIOUS ***
//myobject.method1();
//myobject.method2();
//myobject.method3();
}
const int testMyclass(const int nReps)We start with a function where the object to be leak-tested is instantiated and goes out of scope, and nothing else. We call it from a loop. Start by exercising it five times, then 50, then 500, then 5000. As you eliminate memory leaks from the constructor and destructor, increase the repetition number. Then, when the constructor and destructor are sound, comment in the methods one or a few at a time, and test them starting with 5 reps. When testMyclassOnce() includes complete exercise of all the member functions, and can be called 5000 times without a memory leak or a crash, you can be pretty sure of the class.
{
int nLeaks = 0;
long lMemLast = 0;
long lMemOrg=0;
long lMem;
for(int nT = 0; nT < nReps; nT++)
{
testMyclassOnce();
lMem = coreleft(); //or whatever reports memory in your compiler
if(lMemOrg == 0)
{
lMemLast = lMem;
lMemOrg = lMem;
}
if(lMem != lMemLast)
{
nLeaks++;
cout << "LEAK FOUND ";
}
cout << "Memory left=" << lMem << ", last time=" << lMemLast << " \r";
}
cout << "\nFinal memory=" << lMem << ", initial memory=" << lMemOrg << '\n';
return(nLeaks);
}
Applications where all classes are tested this way are pretty likely to run cleanly.
void main(int argc, char *argv[])Note that this won't work for MFC apps, where it's demanded that there only be one application object. Nevertheless, you can often use this principle for entire subroutines, methods, etc.
{
for(int nT = 0; nT < 5000; nT++)
mymain(argc, argv)
}
void displayFilename(const char *szName, const char *szDir)
{
ostrstream ost;
ost << szDir << '\\' << szName;
ost.put(0);
cout << ost.str() << '\n';
//ost.rdbuf()->freeze(0); //WITHOUT THIS, YOU HAVE A MEMORY LEAK
}
void main()You'll note that the memory has decreased, because ost.str() allocated the string, and that string wasn't destroyed when ost went out of scope. The solution is to call ost.rdbuf()->freeze(0), which "unfreezes" the string and allows it to be destroyed along with the ostrstring object.
{
cout << "Coreleft=" << coreleft() << '\n';
displayFilename("win.ini", "c:\\windows");
cout << "Coreleft=" << coreleft() << '\n';
}
How about when the string is returned by the function. Obviously the function can't destroy it. In such a case, you have to destroy it yourself:
const char *makeFilename(const char *szName, const char *szDir)
{
ostrstream ost;
ost << szDir << '\\' << szName;
ost.put(0);
return(ost.str());
}
void main()In the makeFilename() example above, it might be tempting (and more modular) to let makeFilename itself destroy the string using ost.rdbuf()->freeze(0). Unfortunately, this would expose the returned string to use by another memory allocation, which would overwrite the string and produce self-modifying code. Note also your calling function can't forget to get the pointer, then deallocate it with a call to the next:
{
cout << "Coreleft=" << coreleft() << '\n';
const char *pchFile=makeFilename("win.ini", "c:\\windows");
cout << "Filename is" << pchFile << '\n';
//zap(pchFile); //WITHOUT THIS, YOU HAVE A MEMORY LEAK
cout << "Coreleft=" << coreleft() << '\n';
}
cout << "Coreleft=" << coreleft() << '\n';
cout << "Filename is" " << makeFilename("win.ini", "c:\\windows") << '\n';
// NOTE THAT NOW THE STRING IS LOST FOREVER!
pchFname = makeFilename("win.ini", "c:\\windows"); //SECOND STRING!!
zap(pchFile); //SECOND STRING DESTROYED, BUT FIRST ONE STILL ALLOCATED
cout << "Coreleft=" << coreleft() << '\n';
First of all, a third party tool should do one thing and do it well. That means it should have one, or at the most a handful, of classes. If it has tons of classes, and especially if there are entanglements between the classes (each must know about the other), those tools are a recipe for disaster. So lets assume your vendor ships you one or a few INDEPENDENT classes. Simply memory-leak test each class the way you'd test one of your own. Only much more rigorously. You can't fix it later if there's a memory leak.