• 11/20/02 03:12 PM
    Sign in to follow this  

    Understanding Pointers: A Game Developer

    General and Gameplay Programming

    Myopic Rhino
    Last Updated November 12, 2002


    [size="5"]Introduction

    Hi, welcome to the crazy land of C++ pointers. This article exists as a reference and tutorial for those who are learning C++ and need a little extra help in the area of pointers. It is not an all-encompassing treatise by any means. It assumes you have basic knowledge of variables (including how they're stored in memory), arrays (including null-terminated char arrays), and classes. File I/O experience would be nice, but is not required.


    [size="5"]"What are pointers, and what are they good for?"

    One of the large issues with pointers is that it's hard to create large enough examples to justify the use of them while still keeping everything neat and explicable. Combine that with the quirky notation, and it's enough to give any newbie a headache. We won't cover all the in-depth and technical topics, but we'll hopefully get enough information to clear up the confusion. The best cure is time (along with plenty of reading :) Anyway, here goes.

    A pointer is basically a variable whose value is a memory address. Its size is 4 bytes (in 32-bit OS's anyway), in accordance with the amount of space required to represent a location in memory. The power of pointers becomes apparent when you want to have access to multiple objects of one type while your application is running (a problem normally tackled with plain arrays), but you don't know while coding just how many of them you'll need. For example, if I have a CEnemy class that stores the attributes of my enemies, and I want to be able to read a file that tells me how many enemies there are, their hit points, their locations, etc, I can do it one of two ways:
    1. Allocate a static array (e.g. CEnemy enemyList[255]) that has enough slots for the maximum number of enemies I'll ever have in the game at one time.
    2. Dynamically allocate an array with the number of enemies I read in from the file (e.g. CEnemy *enemyList[numEnemiesInFile])
    Now, there's nothing wrong with number one, except that it requires you to know the greatest number of enemies you'll have in the game at once, and it can be a significant waste of memory.

    Number two is a good solution to the problem. I can read in the number of enemies via an ifstream (InFile >> numEnemiesInFile), and simply "allocate" (make room for in memory) an array of the perfect size.


    [size="5"]Pointer Syntax and Memory Allocation

    Onto syntax. We declare a pointer like this (where [lessthan]data type> is a data type, and ...yeah):

    * = 0;
    We set the pointer to 0 initially because when variables (pointers being no exception) are declared, they have a value that is, for our purposes, random junk. To avoid horrible calamity from occurring through this so-called "wild pointer" (a pointer to no meaningful address), we make it a "null pointer" by setting it to 0. Once again: always initialize your pointers to 0 if you don't assign them some value immediately (e.g. by means of an allocation)!

    We make use of the pointer by giving it a memory address to point to. We can either allocate memory using the 'new' keyword, or we can assign it the memory address of an object we already know. Here are two examples:

    (1)
    CEnemy *enemyList = new CEnemy[5];

    (2)
    CEnemy *someEnemy = &anEnemy;
    Example 1 creates a pointer and points it to an array containing 5 elements that we just allocated via new. The syntax of the right-hand side is simply: (a) the keyword 'new' (b) the data type and (C) square braces containing the number of objects you want to allocate.

    Example 2 requires a little more explanation. You'll notice that the pointer someEnemy is declared in exactly the same as enemyList was in the previous example. However, it doesn't point to an array like enemyList; instead it points to a single CEnemy object named anEnemy. Here's the difference: in reality, all pointers point to a single object, since they can only store a single memory address. The internal mechanics of a pointer allow you to access the elements of an array to which it points, but the pointer itself is blind to how many objects it's responsible for - that is, the number of elements in the array it's pointing to, or even if it's pointing to an array at all (as opposed to a single object). Sorry to ramble on that topic, but it's a stumbling block for many people to mentally separate the concept of the pointer itself from the memory it points to. Perhaps this is because there's no real-world analogue.

    Anyway, here's something else interesting about example 2: the entrance of the "address of" operator - '&'. The ampersand preceding anEnemy's name means "the location at which this object exists." This is very convenient, since it allows us to make a pointer point to a static object.

    I keep using the words "static" and "dynamic," and you're probably missing something if you're not firm on their meaning. When I say static, I mean something that doesn't change during "run-time," (while the program is running). Dynamic is the opposite, meaning "on-the-fly" so to speak. All objects take up memory, but static objects have memory given to then automatically when they are declared. Objects in dynamic memory must be allocated their memory at run-time, because before then, the computer doesn't know how many objects there will be. Pointers exist as the interface between the programmer and the dynamic memory they use.


    [size="5"]Using The Data Stored in the "Pointed-to" Object

    So, what good are pointers if all they do is point to memory? In this section we'll learn the ways of accessing the data that a pointer points to.

    You should already be familiar with the dot operator used to access data members of classes and structs. Its usage looks like this:

    anObject.aMember = aValue;
    Nice and simple. With pointers, there's a slight complication: a pointer is nothing but a variable that holds a memory address. How can we access the data stored at that address? The answer is in a little thing called 'dereferencing.' Dereferencing is the process of getting access to the object that a pointer points to, and we can do it one of two ways: explicitly and implicitly.

    Explicit dereferencing looks like this:

    (*aPointer).aMember = aValue;
    Yuck, you may be thinking, but let's take a look at what's going on. The asterisk (*) before the name of the pointer tells the computer that we want to get the object stored in the address of the pointer. You may recall that the asterisk is also used when declaring a pointer. This is a somewhat odd and confusing quirk; you just have to realize that the asterisk's meaning is dependent on its context. By the way, the parentheses around *aPointer are necessary because of operator precedence. I won't go into detail here, but once you see more advanced examples, you'll understand why that's the case. Onto implicit dereferencing:

    aPointer->aMember = aValue;
    You can see why this is the more popular method! The arrow (->) is composed of a hyphen and a right angle bracket (AKA the "greater-than" sign). This tells the computer that we want it to perform the dereference transparently and give us what appears to be direct access to the object pointed to by the pointer.

    Something to take note of: when accessing an individual object in a dynamic array, use the following syntax:

    aPointer[elementNum].dataMember;
    This form is used because the square brackets automatically dereference the pointer, giving you direct access to the object. The reason for needing elementNum is obvious: you must specify which element of the array you wish to access; without the square braces, the pointer points to the first element of the array.

    And there you have it - data access via pointers.


    [size="5"]Deleting Dynamically Allocated Objects

    You must release all the memory that you dynamically allocate! That is, since you set aside memory for objects in your code, you have to tell the operating system when you're done with it. You do this by way of the 'delete' keyword. Here are a couple of examples:

    (3)
    delete [] enemyList;

    (4)
    delete aRandomEnemy;
    What, you may be asking, is the difference? Well, the code in example 3 deletes an array, while example 4 deletes a single object. If you created a pointer to an array allocated with new, you must use the square brackets as shown - [] - to signify to 'delete' that you're deleting an array. This is important when allocating arrays of a custom type, because the brackets ensure that the destructor of each element is caused. Delete alone will erase the memory, but if the destructors of the individual objects are not called, any shutdown or deallocation they need to perform will be ignored, opening the door wide for memory leaks. [] doesn't care how many elements are in the array, and this is good because you don't have to keep track of it. Example 4 simply deletes the single object pointed to by aRandomEnemy.


    [size="5"]Pointer Caveats

    Ah, to every good thing there is a bad side. Pointers are invaluable when used properly, but can be a complete pain when used even slightly improperly. Let's discuss some ways pointers can be abused. Example 5:

    (5)
    CEnemy *enemyList = new CEnemy[5];
    enemyList = new CEnemy[3];
    These lines are completely syntactically and programmatically legal (that is, neither the compiler or the operating system will complain), but we've just created what's known as a "memory leak." You see, enemyList started out pointing to the first 5 enemies we created, but then we assigned it the memory location of an array of 3 more enemies. Think about it: is there any way now to access the original 5 enemies? No! Because we've thrown away the memory location of the 5 enemies by storing a new value in enemyList, we can never gain access to them, and furthermore, we can never delete them. This will likely not cause a crash, but as stated, it will form a memory leak which can cause your computer to slow down, and it's just generally bad coding form. Point: never lose the address of dynamically allocated memory!

    (6)
    CEnemy staticEnemy;
    CEnemy *pointerToEnemy = &staticEnemy;
    delete pointerToEnemy;
    Uh-oh. Here we've attempted to delete a static object. Even though we used a pointer to access it, staticEnemy is not dynamic. This mistake is most likely caused by the mind's close association between pointers and dynamic memory. Although the two topics are very closely related, not all pointers point to dynamically allocated memory, and if you try to delete a static object by this method, you may cause your program to crash by way of an "access violation," which is your computer's way of telling you "I don't have access to that data!"

    (7)
    CEnemy *enemyList = new CEnemy[12];
    ...
    delete enemyList;
    This is a common error - attempting to delete an array without using the array deletion syntax. This can create memory leaks. Point: don't forget the []'s!

    (8)
    CEnemy *runningOutOfPointerNames = new CEnemy;
    There's something we haven't seen before - using new to allocate just one object. Although you more often see blocks of memory being allocated at a time, you will sometimes need to create a pointer to just one object. This is perfectly fine - just remember to delete it without using square braces; doing otherwise may cause an access violation!

    Okay, that's enough of what you can do wrong. Since this is already way longer than I intended in the first place, let's discuss


    [size="5"]Additional Uses For Pointers

    There are, of course, many things you can do with pointers that I haven't even mentioned yet. One of these is creating arrays of base types with new. The syntax is the same:

    (9)
    int *intPointer = new int[100];
    //or
    char *charPointer = new char[fileSize];
    The first line dynamically allocates an array of 100 integers. The second creates an array of fileSize chars. Char pointers are often used to store data that has been read from files, etc.

    Okay, one final example summarizing what we've learned.

    (10)
    char *string = new char[80];
    strncpy(string, "Hi, I'm a string!", 18);

    char *temp = string;

    do
    {
    std::cout << (*temp);
    } while(*(++temp));
    Hmm, that introduces a lot of new things, but if you take everything we've talked about into consideration (along with what I'm about to tell you), it'll make a lot of sense. All we did was make a char pointer point to a dynamically allocated array of 80 elements, then copied the string "Hi, I'm a string!" into it. Then we created another pointer named temp to point to the "beginning" of string. Then we looped through the elements of the string array, outputting each one. The only confusing part might be what's inside the while conditional, so let's break it down:

    (*(++temp))
    Listen closely: when you increment a pointer, it knows internally to go to its next element. "How can this be," you say, "when we have to keep track of where the pointer points in order to be able to delete it when we're finished?" Well, that's why we made temp in the first place. String still points to the original location, but once we're done with temp, temp will be pointing at the last element of the array. Continuing, we simply explicitly dereference temp to get the character at the element it currently points to; if it's equal to 0, we exit the loop (since, of course, all strings end with a "null", sometimes denoted as '\0', which is equal to the ASCII value 0, literally).


    [size="5"]Epilogue (or, "Is it really over?")

    Well, that's just about everything I can say about pointers. There are many other references about pointers available on the 'net; some of them are probably much better than this one. The best thing you can do for yourself is simply to read as much as you can, and of course code as much as you can, until it clicks. And it will. You will eventually get everything if you just keep at it. If you have any specific questions, my preferred mode of contact is e-Mail; my address is someone_at_gdnmail.net Thanks for reading, I hope you learned something, and bye-bye for now.

    The author would like to thank Scott Hilbert and Greg Rosenblatt for their corrections and proofreading.


      Report Article
    Sign in to follow this  


    User Feedback

    Create an account or sign in to leave a review

    You need to be a member in order to leave a review

    Create an account

    Sign up for a new account in our community. It's easy!

    Register a new account

    Sign in

    Already have an account? Sign in here.

    Sign In Now

    There are no reviews to display.