LinuxDevCenter.com

oreilly.comSafari Books Online.Conferences.

We've expanded our Linux news coverage and improved our search! Search for all things Linux across O'Reilly!

Search
Search Tips

advertisement

Listen Print Discuss Subscribe to Linux Subscribe to Newsletters
Linux & Unix > Excerpts >

C++ Memory Management: From Fear to Triumph
Pages: 1, 2, 3, 4

Understanding C++ Memory Errors

The first step to good C++ memory management is understanding the major errors that are common in this area. An overview is given here; there are also a number of excellent books that cover memory errors in C++. A bibliography with annotations is provided towards the end of this article.



Even when presented in a condensed form, there are enough subtleties in C++ memory management to result in quite a number of warning and caveats. The key is not to get discouraged at the start. After describing the potential dangers, subsequent articles in this series will proceed to show you how to overcome them with several simple, straightforward techniques. You will also see how the power of good software design can go beyond merely coping with problems, and turn C++ memory management into a powerful tool.

As an aside, note that utilities are available to help detect memory-related errors. This series of articles, however, focuses on what you can do when you design and code your programs. Together with a good design and careful coding, the tools can certainly add value. The first strategy, however, must always be to architect memory errors out of your system. This is the only way to make programs that work great, rather than ones that just barely pass a test. In the end, the best tool is still between your ears.

A Memory Error Taxonomy

Memory errors come in two basic types: the dangling reference and the memory leak. The former happens when memory is freed up, but some other code still maintains a reference or pointer to the released area as though it were still allocated. The latter happens when a design calls for allocation and deallocation of memory, but the deallocation step is left out (maybe only in some places) by mistake. The program keeps allocating memory, but does not free all of it; the amount of total available memory in the system keeps going down until something critical breaks because it cannot get the memory it needs.

Strictly speaking, a dangling reference is part of a larger set of errors sometimes known as the wild pointer [Cli95]. A wild pointer can also be generated by forgetting to initialize a local pointer variable (i.e., it then points at some random location), or by setting the pointer to an incorrect value. Fortunately, these errors are typically eliminated by very basic good programming practices. Dangling references are much more complex and subtle.

Out of the two major types of non-trivial memory errors, dangling references are by far the deadliest — they are hardest to debug, and the least susceptible to detection by automated tools. While you should not forget about the danger of memory leaks, dangling references should be your first concern.

From Memory Leak To Dangling Reference — a Classic Example

Here is a classic case of how a memory leak is introduced into a C++ class implementation. Unfortunately, the fix causes something even worse: a dangling reference.

First, the original code, which causes the leak.

Example 1. A Common Memory Leak

//*** A TYPICAL MEMORY LEAK *** 

#include <iostream>  //N.B. no ".h": new-style include.
#include <cstring>
using namespace std; //Everything in 'std' is accessed directly.

//A simple string class.
class SimpleString {
  
public:

  explicit SimpleString(char* data = "");  //Use 'explicit' keyword to disable
                                           //automatic type conversions --
                                           //generally a good idea.

  virtual ~SimpleString();   //Virtual destructor, in case someone inherits
                             //from this class.

  virtual const char* to_cstr() const;  //Get a read-only C string.

  //Many other methods are needed to create a complete string class.
  //This example implements only a tiny subset of these, in order
  //to keep the discussion focused.

  //N.B. no 'inline' methods -- add inlining later, if needed for
  //optimization.

private:
  char* data_p_; //Distinguish private class members: a trailing underscore
                 //in the name is one common method.
  
};


//Constructor. 
SimpleString::SimpleString(char* data_p) :
  data_p_(new char[strlen(data_p)+1]) {
  strcpy(data_p_,data_p);
}

//Destructor.
SimpleString::~SimpleString() {
  //OOPS, forgot to delete "data_p".
}


//Returns a read-only C string representation.
const char* SimpleString::to_cstr() const {
  return data_p_;
}


int main() {
  //Create a local SimpleString.
  SimpleString name("O'Reilly Onlamp");

  //Print it out.
  cout << name.to_cstr() << endl;

}

//*** END: A TYPICAL MEMORY LEAK ***

As you can see, the memory allocated in SimpleString's constructor was not released in the destructor. This is a common mistake. When a SimpleString object is destroyed, the the memory pointed to by data_p_ is simply lost. It seems that a simple change (deleting the memory in the destructor) will fix this problem. It does, but the dreaded dangling reference is now introduced.

Example 2. A Common Dangling Reference

//*** A TYPICAL DANGLING REFRENCE *** 

#include <iostream>  //N.B. no ".h": new-style include.
#include <cstring>
using namespace std; //Everything in 'std' is accessed directly.

//A simple string class.
class SimpleString {
  
public:

  explicit SimpleString(char* data = "");  //Use 'explicit' keyword to disable
                                           //automatic type conversions --
                                           //generally a good idea.

  virtual ~SimpleString();   //Virtual destructor, in case someone inherits
                             //from this class.

  virtual const char* to_cstr() const;  //Get a read-only C string.

  //Many other methods are needed to create a complete string class.
  //This example implements only a tiny subset of these, in order
  //to keep the discussion focused.

  //N.B. no 'inline' methods -- add inlining later, if needed for
  //optimization.

private:
  char* data_p_; //distinguish private class members: a trailing underscore
                 //in the name is one common method
  
};


//Constructor 
SimpleString::SimpleString(char* data_p) :
  data_p_(new char[strlen(data_p)+1]) {
  strcpy(data_p_,data_p);
}

//Destructor
SimpleString::~SimpleString() {
  //N.B. Use of 'delete []' corresponds to previous use of 'new []'.
  //     Using just 'delete' here would be a disaster.
  delete [] data_p_;
}

//Returns a read-only C string representation.
const char* SimpleString::to_cstr() const {
  return data_p_;
}


int main() {
  //Create a local SimpleString.
  SimpleString name("O'Reilly Onlamp");

  //Print it out.
  cout << name.to_cstr() << endl;

  //Dynamically create another SimpleString; make it a copy of the local one.
  SimpleString* name_copy_p = new SimpleString(name);
   
  //Print out the copy.
  cout << name_copy_p->to_cstr() << endl;

  //Print out the original again.
  cout << name.to_cstr() << endl;

  //Delete the copy; set the pointer to null just in case it's used again.
  delete name_copy_p;
  name_copy_p = 0;
  
  //This looks fine... but the results are highly system-dependent.
  cout << name.to_cstr() << endl;
}

//*** END: A TYPICAL DANGLING REFERENCE ***

This program looks innocent at first glance, but look at the output it produces on the author's system.

Example 3. One Possible Result of a Dangling Reference

O'Reilly Onlamp
O'Reilly Onlamp
O'Reilly Onlamp
3"@3"@ Onlamp
Segmentation fault

The first line of the output results from printing the data in the local object name. Then, a copy of name is dynamically allocated; the name_copy_p pointer stores the address of the copy. Printing the data in the copy produces the second line of the output. So far, everything is fine. We can even print the contents of name again — see the third line of the output.

Next, the SimpleString object pointed to by name_copy_p is deleted. Our modified destructor will free the memory buffer (pointed to by the data_p_ member) being used by the object. The original name object still exists, however, so we should be able to continue using it.

Unfortunately, when we try to use name again (the fourth line in the output) something goes terribly wrong. The data has clearly been damaged; it even causes the program to crash (as shown in the last line of the output). Somehow, deleting a copy of name has seriously broken the original!

This code exhibits a classic dangling reference. To understand how this happened, it is helpful to review the key member functions that every C++ class is required to have. The following list describes these methods.

Constructor

Initializes the object during creation. Usually involves allocating resources.

Copy Constructor

A very special constructor, used to create an object that is a copy of an existing object. It is declared like this:

SimpleString( const SimpleString& original );
Assignment Operator

Assigns one fully constructed object to another fully constructed object. Declared like this:

SimpleString& operator=( const SimpleString& right_hand_side );
Destructor

Cleans up the object's internals just prior to deletion. Usually involves freeing up resources.

The most important thing to realize about the methods just listed is that the C++ compiler will generate default versions of them if you do not provide your own. The copy constructor and the assignment operator are particularly easy to forget; unfortunately, the defaults often do not do what you want.

The default copy constructor and assignment operator make a simple, shallow copy of every data member. In the A Common Dangling Reference example, this means that the data_p_ pointer inside of the object stored at name_copy_p will now point to the same chunk of memory as name's data_p_. No attempt is made to allocate more memory and make a deep copy of the data.

When delete name_copy_p; is executed, the SimpleString destructor is called; it frees the memory pointed to by data_p_. Unfortunately, this memory is now being shared with the original object, name. Now, name's data_p_ points at deallocated memory. A dangling reference is born. Figure 1 gives a graphical representation of how this condition arises.

Block Diagram
Figure 1. A Dangling Reference Caused by the Default Copy Constructor

An analogous situation occurs when the default assignment operator is applied to SimpleString objects. Figure 2 illustrates what happens.

Block Diagram
Figure 2. A Dangling Reference Caused by the Default Assignment Operator

In general, three basic strategies are available to deal with the fact that compiler-generated copy constructors and assignment operators are so often dangerously wrong. These are shown in the following list. Subsequent articles in this series will discuss all three approaches in detail.

  • Write your own copy constructors and assignment operators that will work correctly with your classes.

  • Disable copying and assignment altogether by making the copy constructor and assignment operator private.

  • Modify your classes so that the default copy constructor and assignment operator are correct (by using member objects instead of dynamic allocation, or certain types of smart pointers such as the shared_ptr from Boost.org).

Having closely examined a classic sequence of memory errors, let's now look at the other common ways in which such errors can be introduced into a C++ program.

Pages: 1, 2, 3, 4

Next Pagearrow




Tagged Articles

Post to del.icio.us

This article has been tagged:

cpp

Articles that share the tag cpp:

Smart Pointers in C++ (5 tags)

C++ Memory Management: From Fear to Triumph (4 tags)

Regular Expressions in C++ with Boost.Regex (2 tags)

What Is an Iterator in C++, Part 1 (2 tags)

View All

memory

Articles that share the tag memory:

Better Code Through Destruction (13 tags)

C++ Memory Management: From Fear to Triumph, Part 2 (3 tags)

C++ Memory Management: From Fear to Triumph (2 tags)

Memory Management in Cocoa (2 tags)

View All

Recommended for You

Sponsored Resources

  • Inside Lightroom
Advertisement

Sponsored by:

O'Reilly Media

©2010, O'Reilly Media, Inc.
(707) 827-7000 / (800) 998-9938
All trademarks and registered trademarks appearing on oreilly.com are the property of their respective owners.
About O'Reilly
Academic Solutions
Authors
Contacts
Customer Service
Jobs
Newsletters
O'Reilly Labs
Press Room
Privacy Policy
RSS Feeds
Terms of Service
User Groups
Writing for O'Reilly
Content Archive
Business Technology
Computer Technology
Google
Microsoft
Mobile
Network
Operating System
Digital Photography
Programming
Software
Web
Web Design
More O'Reilly Sites
O'Reilly Radar
Ignite
Tools of Change for Publishing
Digital Media
Inside iPhone
makezine.com
craftzine.com
hackszine.com
perl.com
xml.com

Partner Sites
InsideRIA
java.net
O'Reilly Insights on Forbes.com