C++ Memory Management: From Fear to Triumph
Pages: 1, 2, 3, 4
Examples of Common C++ Memory Errors
In addition to the classic case covered
previously,
there are several more aspects of C++ programming that tend to cause memory
errors. The following set of tables describes these situations. Note that the
SimpleString used here is the corrected
version, rather than the broken one used to illustrate dangling references. Article
three in this series will discuss the corrected version of
SimpleString in detail.
Table 1. Errors in Function (or Method) Calls and Returns
| Error | Example |
|---|---|
| Returning a Reference (or Pointer) to a Local Object | |
The local object is destroyed when the function returns. In
general, anything inside curly braces is a scope; if you define a
local object (i.e. non- Result: dangling reference. | |
Returning a const Reference Parameter by const Reference |
|
The C++ compiler is allowed to create an unnamed temporary object
for any Result: dangling reference. | |
| Passing an Object by Value |
|
| When a function parameter is an object, rather than a pointer or a reference to one, then the argument (supplied during a call to the function) for that parameter is passed by value. The compiler will make a copy of the argument (the copy constructor will be invoked) in order to generate the function call. If the argument's type is a derived class of the parameter, then the famous object slicing problem occurs. Any derived class functionality — including overridden virtual methods — is simply thrown away. The function gets what it asked for: its own object of the exact class that was specified in the declaration. While all of this makes perfect sense to the compiler, it is often counterintuitive to the programmer. When you pass an object of the derived class, you expect the overridden virtual methods to be called. After all, this is what virtual methods are for. Unfortunately, it won't work like that if a derived object is passed by value. Result: derived parts are "sliced off." Depending on your design, there may be no memory-related issues, but the slicing problem is important enough to be mentioned here. | |
| Returning a Reference to a Dynamically Allocated Object |
|
Your callers are highly unlikely to take an address of the reference and deallocate the object, especially if the return value is used inside of an expression instead of being immediately saved into a variable. Result: memory leak. |
Table 2. Errors when Defining Methods in Classes
| Error | Example |
|---|---|
| C++ Default Methods | See A Common Memory Leak and A Common Dangling Reference. |
You must make sure that the default methods that C++ generates work well with your design. The default copy constructor and assignment operator tend to cause the most trouble to programmers. This was extensively covered previously. Result: memory leak and/or dangling reference. | |
| The Non-Virtual Destructor |
|
If you intend others to inherit from your class, you must declare
its destructor Result: memory leak; possibly other problems. |
Table 3. Errors in Handling of Allocated Memory or Objects
| Error | Example | |||||
|---|---|---|---|---|---|---|
| Using Arrays Polymorphically |
| |||||
The ability to access a derived object through a reference or
pointer of the base type is central to C++. It allows an important kind of
polymorphism (literally, an ability to assume different forms) in
which Unfortunately, this sort of polymorphism does not work with arrays of classes. C++ inherits its arrays from C — there is basically no difference between an array and a pointer. As you index into an array of derived objects via a base-type pointer, the compiler is happily doing the pointer arithmetic using the size of the base class. The derived class, however, is almost certainly larger than the base class, due to extra members that have been added. Thus, the returned data is rarely a single valid object, but rather pieces of two adjacent ones. Result: wild pointer (recall that dangling references are a special case of wild pointer). | ||||||
| Mistakes Using Casts |
See the | |||||
Casts have been likened to the infamous Result: wild pointer. | ||||||
| Bitwise Copying of Objects |
| |||||
Mechanisms such as the Besides Result: memory leaks and dangling references are both possible, as well as other problems. | ||||||
| Deallocation of Memory That Was Not Dynamically Allocated |
| |||||
It is important to make sure that any memory you free has, in fact,
been dynamically allocated. Actions such as freeing local objects or
deallocating memory more than once will be disastrous to your program. This
also applies to using Result: memory leaks and dangling references, as well as corruption of operating system data structures. | ||||||
| Mismatched Method of Allocation and Deallocation |
|
|||||
Here is a list of common, matched allocation and deallocation methods.
It is a serious error to allocate with one method, and then use something
other than the corresponding deallocation method to release the memory. In
addition, note that You should also never call an object's destructor directly. The only
exception to this is when you allocate the object using the placement
new syntax (e.g. Result: memory leaks and corruption of operating system data structures. |
Table 4. Errors Related to Exceptions
| Error | Example |
|---|---|
| Partially Constructed Objects |
See the aggregated |
When an exception prevents a constructor from running to completion, C++ will not call the destructor of the partially constructed object. Member objects, however, will still be properly destructed. This is not to say that exceptions are to be avoided in constructors. In fact, throwing an exception is usually the best way to indicate that a constructor has failed. The alternative is leaving around a broken object, which the caller must check for validity. While you should often allow exceptions to propagate out of a constructor, it is important to remember that the destructor will never be called in such cases, so you are responsible for cleaning up the partially constructed object (e.g. by catching and then rethrowing the exception, using smart pointers, etc.). Result: memory leak. | |
| Exceptions Leaving a Destructor |
See the |
If a function throws an exception, that exception may be caught by the caller, or the caller's caller, etc. This flexibility is what makes error handling via exceptions into such a powerful tool. In order to propagate through your program, however, an exception needs to leave one scope after another — almost as if one function after another were returning from a chain of nested of calls. This process is called stack unwinding . As a propagating exception unwinds the stack, it encounters local objects.
Just like in a return from a function, these local objects must be properly
destroyed. This is not a problem unless an object's destructor throws an
exception. Because there is no general way to decide which exception to
continue processing (the currently active one or the the new one thrown by the
destructor), C++ simply calls the global Another consequence of an exception leaving a destructor is that the
destructor itself does not finish its work. This could lead to memory leaks.
Your destructor does not necessarily have to throw an exception itself for the
problem to happen. It is far more common that something else called by the
destructor throws the exception. In general, it is best if you make sure that
exceptions never propagate out of your destructors under any circumstances
(even if your compiler implements the Boolean Result: memory leak (destructor does not run to completion); local objects will not be properly destructed if another exception is active. Various resource leaks and state inconsistency are therefore possible. | |
| Improper Throwing |
|
When throwing exceptions, it is important to remember that the object being thrown is always copied. Hence, it is safe to throw local objects, but throwing dynamically allocated objects by value or by reference will cause them to be leaked. Copying is always based on the static type of the object, so if you have a base-type reference to an object of a derived class, and you decide to throw that reference, an object of the base type will be thrown. This is another variant of the object slicing problem covered earlier. A more subtle slicing error occurs when rethrowing exceptions. If you want
to rethrow the exact same object that you got in your You also need to make sure that the copy constructor of the class that you are throwing will not cause dangling references. It is generally not recommended to throw exceptions by pointer; in these situations, only the pointer itself, rather than the actual object, is copied. Thus, throwing a pointer to a local object is never safe. On the other hand, throwing a non-local object by pointer raises the question of whether it needs to be deallocated or not, and what is responsible for the deallocation. Result: object slicing, dangling references, and memory leaks are all possible. | |
| Improper Catching |
|
Improper catching of exceptions can also lead to object slicing. As
you might have guessed, catching by value will slice. The order of the
Result: object slicing; memory-related errors and other problems are
possible if a base-class |

