Most Common Pointer Bugs

Pointer variables give the programmer great flexibility, but they can lead to difficult to find and correct errors in your programs. In this material we will examine the common problems and give hints about how to avoid them. 

In the following we will assume the following types and variable declarations:


typedef double* dblP; 
dblP first = NULL;
dblP second;
dblP third;
double fixed = 4.0;

We also assume that the following code has been executed


second = new double;
*second = 2.0;

Dereference NULL

It is an error to dereference NULL. A SEGFAULT or BUS ERROR will occur at run-time.


cout << *first << endl;	// ERROR, first is NULL

It is just as much a problem to dereference NULL with the -> operator in the case that your type is a pointer to a class or struct. A NULL pointer does not point to anything. Dereferencing obtains the thing the pointer points to.

 

Dangling Pointer

It is an error to dereference a pointer that has not been correctly given a value. This error will probably cause an eventual crash, but after running incorrectly for a while. 


cout << *third << endl;	// ERROR, third has no value

Interestingly, something will be printed most likely. If third happens to have a value that looks to the running program like a valid pointer we will get the contents of a few memory locations interpreted as a double--independently of what they actually contain. It is far worse to say


*third = 3.0;	//	SERIOUS ERROR, changing a random memory location

A pointer can get a valid value by assigning it NULL, or another pointer known to be valid. You can assign it the result of calling operator new. You can also get a valid pointer using the "address of" operator &. Note that if x has type Y then &x has type Y * (pointer to type Y).


third = NULL;
third = second;		//second was valid, so third is also
third = new double;	          // OK, but see Losing Cells, below. 
third = &fixed;		// Third points to fixed.  
			// *third IS fixed 

Aliasing Problems

Aliasing means having more than one name for the same cell (like in third = new double; followed by fourth = third; fourth is another pointer to the same thing as third). Aliasing can add flexibility to our programs, but there are some dangers. For example, after executing:


third = new double;

we will eventually want to execute


delete third;

to reclaim the storage that third occupies. However:

It is an error to delete a cell not allocated with new.


third = &fixed;
delete third;	//	ERROR. third does not point to the free store

It is an error to dereference a pointer after deleting any of its aliases.


third = second;
delete second;
cout << *third << endl;	//	ERROR. third is dangling. 

The above code may still print out a 2.0, actually, since third still points to the same memory location and those locations have not been changed yet. They soon will be however and the above code is considered an error (it is unsafe to refer heap space that has been deallocated, i.e., dereference a dangling pointer).

 The following may produce something very different.


third = second;
delete second;
dblP fourth = new double;
*fourth = 4.0;
cout << *third << endl;	//	ERROR. third is dangling. 

On some systems 4.0 will be produced, since the storage that *second (and *third) occupied previous to the delete may be reallocated to *fourth.

Losing Cells

It is an error to give a new value to a pointer that references a heap location unless you either first (a) create an alias of the heap cell, or (b) delete the heap cell. This is an error that is not caught by the system. It simply results in heap memory locations that can not be recycled for later use in the program. A long running program that allocates but does not free cells will possibly crash when an attempt to allocate a cell cannot be fulfilled.

second = new double;
second = third;	// ERROR	second references a heap location. We cannot get to it anymore now.
                //The pointer to it (second) has been overwritten
		

You can make this safe by first calling 

delete second; or assigning to another pointer the value of second.


delete second;
second = third; //OK

first = second;
second = third;	//OK

Note that giving second a value in other ways (NULL, new...) is just as much an error.

Modern systems throw an exception when the allocator cannot allocate the requested cell. Older systems set the pointer to NULL when you call new and new has no cells to give. 

You should always check that new has been successful (we will learn more about this later).

Deleting Without Allocating

We covered one of these problems above: deleting an object not allocated with new. Note that "delete second" does not free the space occupied by the pointer second. It is not the pointer that is freed. It is the thing that it points to. It is useful to keep this distinction in mind.

It is a logical error to call delete on a NULL pointer. In C++ this is OK though, and the system will ignore it. Doing so sometimes indicates an error in your logic, however. It is an error to delete a dangling pointer. This one is deadly. You might actually damage the heap allocation mechanism itself. It might write garbage data into the cells that the heap allocator uses for its own internal accounting.

FIX THIS !!!
delete third;	// SERIOUS ERROR. Potential heap allocation mechanism damage.

You Do It. (Cautiously). Write a program to test the effect of these errors. Note that some of them (dereferencing dangling pointers) can be very dangerous. If you restrict yourself to retrieving data from such dangling pointers, however, you are extremely unlikely to do any damage. I cannot predict what will happen on your computer with some of these. Make sure that you have copies of all important information. An operating system, such as UNIX, that provides memory protection will greatly limit the potential for damage.