Pointers and Dynamic Memory Allocation
 

Pointers and Dynamic Memory Allocation

Despite all the negative advertising in this book, pointers are invaluable whenever dynamic data structures are involved. Dynamic data structures have to be able to grow and shrink. Growing involves acquiring new areas of memory and cultivating them; shrinking means recycling them. How do we acquire new areas of memory? We have to ask the system to give us memory for a particular type of variable or object. We do it using operator new. The memory returned by new is allocated from the so called heap—the area of memory managed by the C++ runtime. How do we access this new memory? Using a pointer to that particular type of variable or object. For instance, to get memory enough to store a single integer, we'd use the following syntax:
int * pNumber = new int;

Even better, we could (and presumably should) immediately initialize this memory with a particular value we want to store there:
int * pNumber = new int (2001);

Figure 3. New memory for an integer was allocated and initialized with the value of 2001. The pointer pNumber contains the address of this new memory.

The initialization is mandatory when allocating an object that has a constructor. There are two cases: When there is a constructor that takes no arguments you don't have to do anything--such constructor is called by default. An argument-less constructor may, for instance, initialize member variables to some pre-defined values (e.g., zeros).
IStack* pStack = new IStack; // default constructor is called

When the object has a constructor that requires arguments, we specify them in the call to new:
Star* pStar = new Star (1234.5, 10.2);

If we want to store n integers, we need to allocate an array of n integers
int* pNumbers = new int [n];

Figure 4. The result of allocating an array.

There is no direct way to initialize the contents of a dynamically allocated array. We just have to iterate through the newly allocated array and set the values by hand.
for (int i = 0; i < n; ++i)
    pNumbers [i] = i * i;

Consequently, if we are allocating an array of objects, there is no way to pass arguments to objects’ constructors. Therefore it is required that the objects that are stored in such an array have a no-argument constructor. In particular, it's okay if no constructor is defined for a given class-—we are allowed to allocate an array of such objects. No constructors will be called during initialization.

If the class has constructors, one of them must take no arguments (a class may have more than one constructor if they differ by the number or the type of parameters). This no-argument constructor will be then called automatically for every object in the array. For instance, when allocating an array of stacks
IStack* aStack = new IStack [4];
a constructor will be called for each of the four stacks.

To recycle, or free, the memory allocated with new, we use operator delete:
delete pNumber;

If the object being deleted has a destructor, it will be called automatically. For instance,
delete pStar;
will print "Destroying a star of brightness ..."

In the case of arrays, we mark the pointer to be deleted with square brackets. Curiously enough, we put the brackets in front of the pointer name, rather than after it.
delete [] pNumbers;

Again, if we are dealing with an array of objects, the destructor for each of them is automatically called.

Not so in the case of an array of pointers! When the array of pointers is deleted, the objects pointed to by the elements of the array are not automatically deleted. You have to actually go through a loop and delete each of them. The rule of thumb is: Try to allocate memory in the constructor of an object and deallocate it in its destructor. The deallocation should then reflect the allocation. If you had to call operator new in a loop, you’ll have to call operator delete in a similar loop.

Deleting a null pointer is safe. Hint: don’t check for a null pointer before calling delete—delete will do it for you anyway.

What happens if we forget to call delete for a dynamically allocated object or array? The memory it occupies will never be recycled (at least not until the program terminates). We call it a memory leak. Is it a bug? During program termination all memory is freed anyway (although no destructors are called for the leaked objects), so who cares?

It depends. If the program repeatedly leaks more and more memory during its execution, it will eventually run out of memory. So that’s a bug. (And a very difficult one to catch, I might add.) Good programming practice, therefore, is to always deallocate everything that has ever been allocated. Incidentally, there are tools and techniques to monitor memory leaks. I'll talk about it later.