Taken from a forthcoming (I hope) new book
Pointer variables give the programmer great flexibility, but they can lead to difficult to find and correct errors in your programs. In this section we will examine the common problems and give hints about how to avoid them. This should be useful to novice C and C++ programmers. It mentions the allocation operator new. C programmers substitute calls to malloc.
In the following we will suppose the following types and variables
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;
It is an error to dereference NULL. This is the safest error to make, since the system will catch the error 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, struct, or union. A NULL pointer does not point to anything. Dereferencing obtains the thing the pointer points to.
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. It is actually possible to erase a hard disk on some systems (DOS, for example) with this error.
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*.
third = NULL; third = second; //second was valid, so third is also (alias) third = new double; // OK, but see Losing Cells, below. third = &fixed; // Third points to fixed. // *third IS fixed (alias)
Having more than one name for the same cell can also add flexibility to our programs, but there are some dangers. For example, after executing
third = new double;
we will eventually want to execute
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 will probably 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. 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.
It is an error to give a new value to a pointer that references a free store cell unless you either first (a) create an alias of the cell, or (b) delete the cell. This is an error that is not caught by the system. It simply results in free store cells 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 attemp to allocate a cell cannot be fulfilled.
second = third; // ERROR second references the free store.
You can make this safe by first deleting second or assigning 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. It can be tested for, but many programmers improperly ignore the tests.
Deleting Without Allocating
We covered one of these problems above: deleting a pointer not allocated with new. Note that as in the last sentence, we usually say "delete a pointer". Actually, though, it is not the pointer that is deleted. It is the thing that it points to. It is useful to keep this distinction in mind.
It is a logical error to delete 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. C may not be so friendly if you try to free a NULL pointer. It is an error to delete a dangling pointer. This one is deadly. You might actually damage the free store mechanism itself. No. Not a physical mechanism. But it might write garbage data into the cells that the free store uses for its own internal accounting.
delete third; // SERIOUS ERROR. Potential free store 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.