Contents

Chapter 10

Pointers

Pointers in C++ can be an extremely complex topic to beginning programmers because they are an abstract idea but they are extremely powerful topic. They allow us to react to changes in the needs of the user as the program is executing and allow us to create interesting data structures. Pointers are a simple concept but how they are applied and used correctly tends to be detailed laden.

A pointer is just a variable that holds or contains an address. That address might be the address of another variable or even a function. So like any other variable a pointer is allowed to be modified and point to a new address. This is where the flexibility of the pointer really starts to be useful. It is with this utility that we can create dynamic data structures and react to user needs while the program is executing.

Creating Pointers

Like any other variable a pointer needs to be declared before it can be used. To declare a pointer we need a new piece of syntax. The asterisk (*) is used when declaring a pointer and when we are following a pointer. If we wanted to declare a pointer to an int, we would use this syntax:

int * pointerToInt;

or

int* pointerToInt;

or

int *pointerToInt;

or

int*pointerToInt;

The spacing around the * does not matter and are mainly a matter of taste. I prefer any of the first three ways to declare a pointer, but all of the ways will work.

Assigning an Address to a Pointer

Now that we can create a pointer, this creation alone does not give the pointer the address that is pointing to. In fact in C++, if we create a pointer like we did above, this is an uninitialized pointer and if we followed it, we would likely cause a run-time error in our program. Like all variables we have to assign a value to our pointer or it should be considered to not contain anything meaningful.

We can give our pointer its address in two ways. The first way makes use of the address-of operator (&). You might remember this from the pass-by-address and in fact we are passing in the address to the parameter when we use this in our functions. Since a pointer holds an address, we can use this to get the address of a variable and assign it to a variable that can hold an address, i.e. a pointer.

int *pointerToInt;
int x;

pointerToInt = &x;

Using the three lines above we create the pointer to an int and a regular int. When we use the & on x and assign the result to the pointer, we now have an initialized pointer that we can successfully use. We have essentially created a link or alias to x via pointerToInt. We could change the value in x directly using x or indirectly using the pointer. The following lines of code would have the same effect.

x = 10;
*pointerToInt = 10;

In the first line, we are directly using x to assign the value 10 to x. In the second line, we first dereference the pointer and then assign that address location the value 10. Since pointerToInt references the address where x is located, we are indirectly assigning x the value 10. It is this abstraction that makes pointers so strange the first time they are encountered. You may be wondering why we would want to do this. To answer this question we have to look at the second way we give a pointer its address.

Dynamic Memory Allocation

For me a more typical use of a pointer is via dynamic memory allocation. When we write software and compile it, the compiler can layout the memory allocation for all of the automatically created variables and functions. When we have a pointer, we are allowed to use it to point to any memory address in the machine. In order for it to successfully execute, we must make sure we are allowed to access the memory. When we give a pointer its address while the program is executing, we are dynamically assigning an address.

In C++ we need to use the keyword new to request memory from the system dynamically. We can request both individual memory allocations, e.g. space or memory for one integer, or arrays of memory, e.g. an array of 10 integers. For example to request space for a single int we can do this:

int *pointerToInt;

pointerToInt = new int;

The call to new returns a memory address. Since our pointer stores memory addresses, this assignment works as we want. We request the memory and it is stored in the pointer variable. Our new integer memory comes from a different stack of memory then our regular integers. In C++, there are two separate memory allocation locations. Up until now, all of our memory have come from the stack or the run-time stack. When we use new the memory is said to come from the heap or the free store. If we wanted to allocate an array of integers instead of a single int, we would do something like this:

int *pointerToInt;

pointerToInt = new int[10];

So in this allocation we still declare the pointer in the same fashion and when we request the memory we add to it the brackets and a number in the brackets. This would dynamically allocate an array of 10 integers. Once we have allocated the array, the array would work just like all the other arrays we've seen. The bracket notation contains within an implied dereferencing operation. It should be noted that when we dynamically allocate an array we are allowed to use a variable within the brackets.

With this second way of requesting memory, we have finally solved the second problem with arrays, i.e. that arrays are fixed sized. Since the only thing the compiler needs to "know" about an array is the address of the first element, we can use a pointer to point to an array. When the array fills up, we can create a new larger array. This allows us to create the appearance of an array that "grows" because when the array fills up, we can allocate a new larger array and copy the data from the old full array into the new larger one. Since pointers are variables, we can then point to the new larger array and go on as if our array never got full. We can do this as long as there is memory in the machine that we can request.

Cleaning Up Our Memory

Since we have to request the memory from the system, we are also tasked with cleaning up the memory after we are done with it. We do this using the delete keyword. Just like there are two ways to request the memory there are two ways to release the memory, depending on how we created it.

If we request a single unit of the memory, for example:

int *pointerToInt;
pointerToInt = new int;

Then we delete it this way:

delete pointerToInt;

This will return the memory to the system and any use of the pointer before it is assigned a new address would be an error.

If we request the memory as an array like this:

int *pointerToInt;
pointerToInt = new int[10];

Then we delete it this way:

delete [] pointerToInt;

Notice in this case we use the [] between the keyword delete and the name of the pointer. This will indicate to the system that we are returning an array, not a single unit memory.

Pointers and References

Pointers and references are similar but different. Both pointers and references store addresses. The biggest difference between a pointer and a references is that the reference is a constant address, i.e. it can't be changed once it's been created. The pointer is a variable and can be changed. It is much more flexible and allows for many various configurations. A pointer can be put in a class that is a pointer to an object of the same class. In this way, you can create self-referential structures like lists and stacks.

Summary

We've barely scratched the surface of pointers and how they are used within this chapter. We started to dance around the idea of a data structure and how they are constructed. I often will describe C++ like an iceberg. Most of this text has been related to the part of the iceberg that is floating above the water. Once we start discussing pointers and classes, especially when you start putting pointers inside of classes, you begin to dip below the water line. Most of C++, like most of the iceberg, is under the water. C++ is a vastly complex and huge language. It is impossible for any one programmer to know all of C++ and it shouldn't be attempted to know the entirety of the language. The language is growing and changing every year. Instead a programmer should focus on the how good software is assembled and worry about the details only as needed. There are always multiple ways to put a C++ program together. The only wrong way to use C++ is to not use C++.


Written by: David McPherson © 2019

Creative Commons License
This work is licensed under a Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License.

hit counter of smallseotools