FAQ 31.02 What is the easiest way to avoid memory leaks?

graphics/new_icon.gif

Place pointers inside objects and have the objects manage the pointers.

The pointer returned from new should always be stored in a member variable of an object. That object's destructor should delete the allocation.

The beauty of this approach is that objects have comprehensive creation and destruction semantics (including constructors and destructors), whereas pointers have extremely rudimentary creation and destruction semantics. By putting pointers in objects it is possible to guarantee that the destructor will always be executed and the memory will be properly deallocated.

In the example that follows, whitespace-terminated words are read from the standard input stream cin (see FAQ 2.16), and the unique words are printed out in sorted order. For example, if the standard input stream contains the words "on and on and on he went," the output will contain the unique words "and he on went."

The preferred way to produce a sorted list of unique words is to use the string class (see FAQ 2.16) and a container class (in this case, the standard set<T> template; see FAQ 28.14):

 #include <iostream> #include <set> #include <string> using namespace std; void theRightWay() throw() {   typedef set<string> StringSet;   StringSet unique;   string word;   while (cin >> word)     unique.insert(word);   for (StringSet::iterator p = unique.begin();        p != unique.end(); ++p)     cout << *p << '\n'; } int main() { theRightWay(); } 

Note that there are no explicit pointers in this code, and there are no chances for memory leaks. For example the string object contains a char*, but there is no possibility of a leak since the string object is local and it has a proper destructor it handles its own memory management. Similarly for the set<string>: this object may contain many pointers and may use many memory allocations, but since the object is local and since it has a proper destructor, it manages its own memory without any possibility of memory leaks.

In contrast, the undesirable approach would be to use explicit pointers to explicitly allocated memory. The following example is more or less equivalent to the example shown above, but it is rife with opportunities for both wild pointers and memory leaks. Making this code safe would be quite a bit more difficult than the relatively simple solution shown above (the problems cited are described after the code):

 // a and b point to char*s int compareCharPtr(const void* a, const void* b) throw() { return strcmp(*(char**)a, *(char**)b); } void theWrongWay() throw() {   const unsigned maxNumWords = 1000;   const unsigned maxWordLen = 100;   char word[maxWordLen];   unsigned uniqueSize = 0;   unsigned uniqueCapacity = 100;   char** unique = (char**) malloc(uniqueCapacity * sizeof(char*));   if (unique == NULL)                                <-- 1     return;   while (cin >> word) {                              <-- 2     unsigned i;     for (i = 0; i < uniqueSize; ++i)       if (strcmp(unique[i], word) == 0)              <-- 3         break;     if (i == uniqueSize) {       if (uniqueSize == uniqueCapacity) {         uniqueCapacity *= 2;         unique = (char**)realloc(unique, uniqueCapacity * sizeof(char*));         if (unique == NULL)                          <-- 4           return;       }       unique[uniqueSize++] = strdup(word);     }   }   qsort(unique, uniqueSize, sizeof(unique[0]), compareCharPtr);   for (unsigned i = 0; i < uniqueSize; ++i)     cout << unique[i] << '\n';   for (unsigned j = 0; j < uniqueSize; ++j)     free(unique[j]);   free(unique); } 

(1) Problem 1

(2) Problem 2

(3) Problem 3

(4) Problem 4

Most programmers will notice these major problems with this code.

  1. If it runs out of memory in the malloc() step, it should probably throw an exception rather than silently returning; see FAQ 12.06.

  2. If the length of a word exceeds maxWordLen, the program overwrites memory and probably crashes.

  3. If the file contains null bytes ('\0'), strcmp() and strdup() give the wrong answers.

  4. If it runs out of memory in the realloc() step, all the previously allocated memory is lost a leak. Plus the routine should probably throw an exception rather than silently returning; see FAQ 12.06.

All these problems can be fixed, but fixing them makes the code even more complex. Note that theRightWay() doesn't have any of these problems and is much simpler. It properly handles running out of memory, and it can handle arbitrarily long words, arbitrarily many characters within long words (including null bytes), and arbitrarily many words.

The more subtle problem with theWrongWay() is its use of explicit pointers. If a maintenance programmer changes the code so that it exits before the last for loop, such as throwing an exception, perhaps from another routine, this code will leak memory. The code could protect against exceptions with a large try block around the whole routine except the last for loop, but it would be much harder to protect against an early return. Note that theRightWay() doesn't have this problem: it won't leak when an exception is thrown or an early return is executed.

But even if theWrongWay() is made safe, it will still be much slower than theRightWay(), especially with large numbers of unique words. This is because theWrongWay() uses a linear search (doubling the number of unique words typically quadruples the time); theRightWay() uses a much more efficient algorithm.



C++ FAQs
C Programming FAQs: Frequently Asked Questions
ISBN: 0201845199
EAN: 2147483647
Year: 2005
Pages: 566
Authors: Steve Summit

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net