Chapter 7: Pointers and Dynamic Memory Allocation
Deleting a pointer means releasing all memory addressed by the pointer. Once this has been done, you can no longer access the storage. A class destructor is usually responsible for deleting any dynamic storage used by instances of the class.
Casting a pointer to a base object into a pointer to a derived object is valid only when the exact type of the object being addressed is known. The converse, implicitly casting a pointer to a derived object into a pointer to a base object, is much safer.
A copy constructor creates a new object that is a duplicate of an existing object of the same class. A copy constructor runs automatically in three cases:
Cross-linking two pointers to the same storage location is dangerous at best, and can lead to serious runtime errors. To prevent that from happening, always provide a copy constructor for a class containing pointers.
The implementor of a class should never depend on users of the class to anticipate problems with copy and assignment semantics. That is purely the implementor's job.
Common Pointer Errors
We would like to highlight some of the more common mistakes made by programmers as they are learning to master pointers in C++.
Encapsulating the new Operator
We're going to make a bold statement here: Avoid the use of the new operator in user code, that is, outside classes. Memory allocation errors can be introduced into programs when you allocate storage and later forget to release it. For example, something as simple as the following would cause a memory leak that could take considerable debugging time to track down:
The problem is, of course, that we neglected to release the dynamic memory addressed by temp before it went out of scope. On the other hand, if we had created a LongArray object, the class destructor would have taken care of the memory deallocation automatically, and there would be no memory leak:
The case for avoiding the direct use of the new operator will be even more compelling in Chapter 8, when we introduce exception handling. To produce programs that gracefully recover from memory allocation errors, the new operator should always be enclosed in a class.
Uninitialized Pointer
Deleting storage indicated by a pointer that was never initialized is a serious error. So is deleting the same pointer twice. But deleting a null pointer is guaranteed in standard C++ not to have any effect. For this reason, we recommend setting a pointer to null when it does not point to actual data. In the next example, the second attempt to deallocate p will have no adverse effect:
Standard C/C++ libraries also include the malloc and free functions that allocate and release memory blocks. We recommend that you avoid mixing these with the new and delete operators. If you use malloc to create an array, for example, C++ doesn't guarantee that information about the array's size will be saved when the array is later deallocated. For example,
Null Pointer Assignment
A common programming error is made when attempting to dereference a null pointer and use it to access memory. Memory may be corrupted, causing the program to behave strangely; a program may or may not run to completion, and if it does, the runtime system will usually display a "null pointer assignment" error message at the end. This type of error is difficult to track down because the error message does not identify the particular program statement that caused the problem. It is always a good idea to check a pointer for a null value. For example,
Dangling Pointer
A dangling pointer is a pointer that no longer contains the address of allocated storage. One way to cause this error is to create a local variable and then try to use the variable's address outside the block. In the next example, the function returns a pointer to an automatic array called buffer. But the latter is destroyed when control passes out of the function:
The pointer returned by Input is a dangling pointer because it no longer refers to a valid address. Any attempt to use this pointer will be a serious program bug, often resulting in a memory protection error or core dump.
After the storage addressed by a pointer has been deallocated, it may even appear to contain valid data. We call these data ghosts because they appear to be real, but they only exist by accident, and accessing them can be a spooky experience. A good habit to develop is to set deallocated pointers to null if you think they might be mistaken for actual addresses. It is easy to test for a null pointer before dereferencing it.
On the other hand, simply setting a pointer to 0 does not by itself release the memory it addresses. The following would leave a useless block of storage allocated on the heap. If no other pointer contains the block's address, the memory cannot be deallocated until the end of the program, thus creating a memory leak: