Pointers have a bad reputation. They can appear complicated or convoluted. Add to this, the way pointers can be used in C++ code has evolved in recent years. I believe the best way to understand them is to use them in their original (old-fashioned) form. You can’t understand how an internal combustion engine works if you go straight to a supercar. So what is a pointer?
[widgets_on_pages id=”udemy_advert_cpp_1″][widgets_on_pages id=”udemy_code_details”]
A pointer is a variable which holds a memory address
That’s it! One of the things that cause their bad reputation is their syntax. In fairness to pointers, once you understand and practice the syntax it ceases to appear convoluted and might even begin to seem elegant. I did say might!
Let’s learn the absolute basics about pointers and then we will put them to use in a real game project that takes full advantage of the main reason we use them. Then in a later tutorial, we will round out our pointer knowledge by learning the new stuff too – Smart pointers.
A quick analogy about pointers. If a variable is a house and its contents are the value it holds, then a pointer is the address of the house. Using this analogy, when we pass values to or return values from a function, we are actually making a new house the exact same as the previous one (except when we use a reference).
So is a pointer is just another type of reference? More accurately, a reference is a type of pointer. When we use references in our C++ code, the compiler uses pointers “behind the scenes” to carry out our intentions. Remember the problem with references?
References must be assigned to a variable when they are declared. This is clearly not very dynamic. When we use pointers explicitly we gain significant power and control. If you want a game with thousands of dynamic game objects you are almost certainly going to need to use pointers to make them work well.
Show me the pointer code already
There are two main operators associated with getting started with pointers. The first is the ‘address of ’operator.
&
And the second is the ‘dereference ’ operator.
*
The first thing you will notice is that the address of operator is the same as the reference operator. However, the operators do different things in different contexts.
Pointers are not clear and immediately obvious when you first start using them! Armed with the knowledge that you need to pay more attention to pointers than to previous syntax as well as what the two operators are (address of and dereference), we can move on.
How to declare a pointer
[widgets_on_pages id=”bcgp_cfgp_gpp”]
To declare a pointer use the dereference operator along with the type of variable the pointer will be holding the address of.
1 2 |
// A pointer to hold the address of an int int* pShields; |
The code declares a new pointer called pShields that can hold the address of a variable of type int. As with other variables a pointer needs to be initialized with a value to make proper use of it.
The name
pShields is arbitrary. It is common practice to prefix the names of variables which are pointers with a
p. It is then much easier to remember when we are dealing with a pointer and to distinguish them from regular variables.
The white space used around the dereference operator is optional but recommended.
Just like a regular variable can only contain data of the appropriate type, a pointer can only hold the address of a variable of the appropriate type. A pointer to type int cannot hold the address of a String or a float etc.
How to Initialize a pointer
Next, we can see how to get the address of a variable into a pointer. Take a look at this next code.
1 2 3 4 5 6 7 8 |
// A regular int variable called shields int shields = 5; // Declare a pointer to hold the address of a variable of type int int* pShields; // Initialize pShields to hold the address of shields pShields = &shields; |
In the previous code, we declare an int variable called shields and initialize it to 5. We can access the address of shields using the address of operator. Look closely at the last line of the previous code where we initialize pShields with the address of shields.
Our pointer pShields now holds the address of the regular int, shields. In C++ jargon we say that pShields points to shields. We can use pShields by passing it to a function so that function can work on shields. But we can do that already with references. There would be no reason for pointers if that was all we were going to do with them.
Reinitializing pointers
A pointer, unlike a reference, can be reinitialized to point to a different address.
1 2 3 4 5 6 7 8 9 10 11 12 |
// A regular int variable called shields int shields = 5; int score = 0; // Declare a pointer to hold the address of a variable of type int int* pShields; // Initialize pShields to hold the address of shields pShields = &shields; // Re-initialize pShields to hold the address of score pShields = &score; |
Now pShields points to the int variable, score. Of course, the name of our pointer, pShields is now slightly ambiguous and should perhaps have been called pIntPointer. The key thing to understand here is that we can do this reassignment.
At this stage, we haven’t actually used a pointer for anything other than simply pointing (holding a memory address). Let’s see how we can access the value stored at the address pointed to by a pointer. This will finally make them useful.
How to dereference a pointer
[widgets_on_pages id=”udemy_advert_cpp_2″][widgets_on_pages id=”udemy_code_details”]
So we know that a pointer holds an address in memory. The actual addresses used by variables are determined when the game is executed and therefore, there is no way of knowing the address of a variable while writing our code. This causes the limitation with references.
We access the value stored at the address pointed to by a pointer by using the dereference operator * . The next code manipulates some variables directly, and by using a pointer. Try and follow along by reading the comments.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
// Some regular int variables int score = 0; int hiScore = 10; // Declare 2 pointers to hold the addresses of ints int* pIntPointer1; int* pIntPointer2; // Initialize pIntPointer1 to hold the address of score pIntPointer1 = &score; // Initialize pIntPointer2 to hold the address of hiScore pIntPointer2 = &hiScore; // Add 10 to score directly score += 10; // Score now equals 10 // Add 10 to score using pIntPointer1 *pIntPointer1 += 10; // score now equals 20- A new high score // Assign the new hi score to hiScore using only pointers *pIntPointer2 = *pIntPointer1; // hiScore and score both equal 20 |
In the previous code, we declare two int variables, score and hiScore. We then initialize them with the values zero and ten respectively. We next declare two pointers to int. They are pIntPointer1 and pIntPointer2. We initialize them in the same step as declaring them to hold the addresses of (point to) the variables score and hiScore respectively.
Next, we add ten to score in the usual way, score += 10. Then we see that by using the dereference operator on a pointer we can access the value stored at the address they point to. The following code actually changed the value stored by the variable pointed to by pIntPointer1.
1 2 3 |
// Add 10 to score using pIntPointer1 *pIntPointer1 += 10; // score now equals 20, A new high score |
The last part of the previous code dereferences both of the pointers to assign the value pointed to by pIntPointer1 as the value pointed to by pIntPointer2.
1 2 3 |
// Assign the new hi-score to hiScore with only pointers *pIntPointer2 = *pIntPointer1; // hiScore and score both equal 20 |
Both score and hiScore are now equal to 20.
The real power of pointers using dynamically allocated memory
All the pointers we have seen so far point to memory addresses that have a scope limited only to the function they are created in. So if we declare and initialize a pointer to a local variable, when the function returns, the pointer, the local variable, and the memory address will be gone. They become out of scope.
Up until now, all our games have been using a fixed amount of memory that is decided in advance of the game being executed. Furthermore, the memory we have been using is controlled by the operating system and variables are lost and created as we call and return from functions. What we need is a way to use memory that is always in scope, until we are finished with it.
When we declare variables(including pointers) they are in an area of memory known as the stack. There is another area of memory, which although allocated/controlled by the operating system, can be allocated at run time. This other area of memory is called the free store or sometimes, the heap.
Memory on the free store does not have scope to a particular function. Returning from a function does not delete the memory on the free store.
This gives us great power. With access to memory that is only limited by the resources of the computer our game is running on, we can plan games with huge amounts of objects. When we use memory on the free store it is our responsibility to delete it when we are done. If we don’t, a memory leak will occur and possibly crash the game.
Let’s look at how we can use pointers to take advantage of the memory on the free store and also how we release that memory back to the operating system when we are finished with it.
To create a pointer that points to a value on the free store first we need a pointer.
1 |
int* pToInt = nullptr; |
In the previous line of code we declare a pointer as we have seen before but as we are not initializing it to point to a variable we initialize it to nullptr. We do this because it is good practice. Consider dereferencing a pointer (changing a value at the address it points to) when you don’t even know what it is pointing to. It would be the programming equivalent of going to the shooting range, blindfolding someone, spinning them around, and telling them to shoot. By pointing a pointer to nothing( nullptr) we can’t do any harm with it.
When we are ready to request memory on the free store we use the new keyword as shown in this next line of code.
1 |
pToInt = new int; |
The pointer pToInt now holds the memory address of space on the free store that is just the right size to hold an int value. It is important to realize that this memory will never be freed (within the execution of our game) unless we free it ourselves. If we continue to take memory from the free store without giving it back, eventually it will run out and the game will crash.
If our game has a loop that requests memory and this loop is executed regularly throughout the game eventually, the game will slow and then crash. This next line of code, hands back (deletes) the memory on the free store that was previously pointed to by pToInt.
1 |
delete pToInt; |
Now the memory that was previously pointed to by pToInt is no longer ours to do what we like with, we must make take precautions. Although the memory has been handed back to the operating system, pToInt still holds the address of this memory which no longer belongs to us.
This next line of code ensures that pToInt can’t be used to attempt to manipulate or access this memory.
1 |
pToInt = nullptr; |
If a pointer points to an address that is invalid it is called a wild or dangling pointer. If you attempt to dereference a dangling pointer, if you are lucky the game will crash and you will get a memory access violation error. If you are unlucky you will create a bug that will be incredibly difficult to find. Furthermore, if we use memory on the free store that will persist beyond the life of a function, we must make sure to keep a pointer to it or we will have leaked memory.
Now we can declare pointers and point them to newly allocated memory on the free store. We can manipulate and access the memory they point to by dereferencing them. We can return memory to the free store when we are done with it and we know how to avoid having a dangling pointer.
How to Declare a pointer to an object
Pointers are not just for regular variables. We can also declare pointers to user-defined types like classes. This is how we would declare a pointer to an object of the type Spaceship.
1 2 |
Spaceship spaceship; Spaceship * pSpaceship = &spaceship; |
We can even access the member functions of an Spaceship object directly from the pointer, perhaps like this code.
1 2 |
// Call a member function of the player class pSpaceship->shoot() |
What you need to know about pointers so far
Pointers are a bit awkward but exceptionally powerful. This brief tutorial has only scratched the surface of a whole world of pointer possibilities.
If you take one thing away from this tutorial you will be able to complete the next game project. Pointers are variables that store a memory address. If you can also remember that we can pass pointers to functions to directly manipulate values from the calling function’s scope, within the called function – then you are on the way to understanding pointers.
Furthermore, if you also understand that we can use pointers to point to memory on the free store so we can dynamically allocate large amounts of memory while the game is running – you will have no problem moving on to more advanced pointer tutorials (when I get around to writing them).
If you Google pointers you will see lots of information about smart pointers. Don’t worry about them for now. Smart pointers use regular pointers we have just discussed. Understanding regular pointers will help your understanding of smart pointers.
It’s time for another game project.
Thank you Mr. Horton, you have helped me to understand pointers better!
My pleasure.
Great tutorials! Where to i go from this? I cant find 11.
Thats all the pure c++ tutorials I have done. I would work your way through all the practical projects here http://gamecodeschool.com/sfml-projects/.
Really great….