Ben’s Golden Rule for Preventing Memory Leaks

As an embedded software engineer, I spend the majority of my time writing code in C and C++. One somewhat-justified knock on these languages is that they both force the programmer to sweat all the details involved with memory management.

One particular bugaboo of the C/C++ engineer is the memory leak: Memory that is dynamically allocated but, because of a programming error, isn’t returned to the system when it is no longer used. On an embedded device, leaked memory will grow over time, consuming more and more of the available resources until there is no choice but to reboot the device. And that’s a really bad thing.

I’m not going to tempt fate by declaring that my code is impervious to memory leaks. Far from it! However, I have developed an approach to dynamically allocated memory that I like to see all my teams follow. With this approach, memory leaks tend to happen much less frequently.

It really boils down to a single, simple idea, which I immodestly call Ben’s Golden Rule for Preventing Memory Leaks:

A pointer variable is either referencing dynamically allocated memory, or it is NULL.

One of the nice things about setting a pointer to NULL is that if you happen to dereference it by mistake, your program will immediately crash. This is a good thing because, hopefully, it means the bug will be rather obvious and thus caught early in the testing cycle.

Here are some simple steps to implement Ben’s golden rule:

  1. Initialize all pointers to NULL. Don’t ever let a pointer hold random junk. Initialize it to NULL even if you are going to assign it in the very next line of code:
     
    SomeComplexObject *my_object = NULL;
    my_object = new SomeComplexObject();
  2. Reset pointers to NULL immediately upon freeing them. Even at the bottom of a function where your variable is about to go out of scope, you should pedantically reset it to NULL after a call to free() or delete. This way, if someone later refactors this code, or extends it, the likelihood of a bug creeping in is much lower.
     
    delete my_object;
    my_object = NULL;
  3. Before assigning anything to a pointer, check to see if it is NULL unless the immediate line of code above the assignment sets it to NULL:
     
    my_structure_t *a_struct = NULL;
     
    // ... lots of stuff here, including possibly code that allocates a_struct
     
    if (a_struct) {
    free(a_struct);
    a_struct = NULL;
    }
    // now we know it's NULL
    a_struct = (my_structure_t *) malloc (sizeof(my_structure_t));

    In some cases, tools like Coverity may complain about redundant checks for NULL. So you will have to be flexible here; if your coding standard (or your customer!) demands that your code be free of such warnings from Coverity, you will have to remove some of the checks. However, be aware that bugs may creep in during maintenance or refactoring.

  4. Be aware that several C library routines may perform “surprise allocations” by calling malloc() internally. This depends on your C library, but typical calls include things such as strdup and vasprintf. If you use such functions in your code, you will need to audit all code paths that use them to ensure that memory is freed in each path.
     
  5. Don’t mix references to dynamically-allocated memory with references to other types of memory within the same pointer. An example of what NOT to do comes from some UI code I found, which I have obfuscated to protect the guilty party.
     
    char *search_key = NULL;
     
    // If MyCheckBox is selected, copy the search_key from the
    // MyTextEntry widget; otherwise, set it to "not_used"
    if (gtk_toggle_button_get_active(MyCheckBox))
    search_key = strdup(gtk_entry_get_text(MyTextEntry));
    else
    search_key = "not_used";
     
    // Ugh! At this point, the rule about whether or not you should
    // call free(search_key) are confusing. Don't do this!
     
    // ... subsequent code does something with search_key ...
     
    free(search_key); // note, this is a bug half the time
    search_key = NULL;
  6. Initialize C++ member variables that are pointers to NULL in your constructor, and in your destructor, release all member-variable pointers, and then immediately set them to NULL.
     
    Storage::Storage :
    mPointerToData(NULL)
    {
    }
     
    Storage::~Storage()
    {
    if (mPointerToData != NULL)
    delete mPointerToData;
    mPointerToData = NULL;
    }
  7. In C++, use std::string instead of char *. The std::string class handles all memory management internally, and it’s fast and well optimized.
    ?
  8. Make sure you match up your [] operators. One odd bug unique to C++ programs can cause mysterious memory leaks. It is easy to allocate an array of items with new [], and then use delete, rather than delete [] to free the memory. Any time you allocate an array with new [], you must audit your code to ensure that the deletion is always done with delete [], rather than just delete. The compiler will usually let you get away with it, but at runtime, you will see memory leaks and allocation arena corruption.

Finally, another helpful design tool for avoiding memory leaks is to very clearly state in the design and code comments where specific items are created, and where they are deleted. Consider making the allocations all go through a single routine, and deletions through another. In C, these can be placed in a single source file, and in C++, you can make them methods of the same object. I’ll have more to say about this in a future post.

Ben Mesander has more than 18 years of experience leading software development teams and implementing software. His strengths include Linux, C, C++, numerical methods, control systems and digital signal processing. His experience includes embedded software, scientific software and enterprise software development environments.