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.
Errors in Function (or Method) Calls and Returns
Error: 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- static) inside of a scope (as shown in the example), it no longer exists after the closing curly brace of that scope.
Result: dangling reference.
Example:
1 2 3 4 5 6 7 8 9 10 | //--- SimpleString& generate_string() { SimpleString localstr("I am a local object!"); //... maybe do some processing here ... return localstr; //As soon as this function //returns, "localstr" is //destroyed. } //--- |
Returning a const Reference Parameter by const Reference
The C++ compiler is allowed to create an unnamed temporary object for any const reference parameter. After all, you promise not to change the parameter when you declare it const. Unfortunately, the unnamed temporary goes out of scope as soon as control leaves the function, so if you return a const reference parameter by const reference, a dangling reference can result.
Result: dangling reference.
1 2 3 4 5 6 7 8 9 10 | //--- const SimpleString& examine_string(const SimpleString& input) { //... maybe do some processing here ... return input; //If "input" refers to a temporary //object, that object will be //destroyed as soon as this //function returns. } //--- |
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.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | //--- //This function slices objects. void the_slicer(Base arg) { arg.example(); } //--- //--- Base base; Derived derived; cout << "Calling 'the_slicer' on a Base" << endl; the_slicer(base); //O.K. cout << endl; cout << "Calling 'the_slicer' on a Derived" << endl; the_slicer(derived); // Slices "derived"; // this is rarely what // you want. //--- |
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.
1 2 3 4 5 6 7 8 9 10 11 12 13 | //--- SimpleString& xform_string_copy(const SimpleString& input) { SimpleString *xformed_p = new SimpleString("I will probably be leaked!"); //... maybe do some processing here ... return *xformed_p; //Callers are highly unlikely //to ever free this object. } //--- |
By George Belotsky Initially published on Linux DevCenter (http://www.linuxdevcenter.com/)