Pointers: Beginning to Intermediate

Published April 24, 2003 by James Poag, posted by Myopic Rhino
Do you see issues with this article? Let us know.
Advertisement
[size="5"]Introduction

The purpose of this article is explain to a beginning or intermediate programmer what pointers are and to provide a few examples of how they are used. The first three sections constitute beginning/working knowledge of the pointer with the exception of the rotate array sample, which can be done better with the STL library. The fourth and fifth sections demonstrate some of the more clever uses of the pointer. The new and delete keywords were intentionally left out of this article. A few of the reasons for their exclusion are as follows:
  • New and Delete are the number one cause of memory leaks.
  • The STL library provides solutions to common problems where new and delete are apart of the initial solution.
  • There isn't enough Aspirin in the world to alleviate the headaches new and delete cause. Author's note about the lexicon: In this article I attempted to avoid introducing keywords or concepts without either explaining them or providing context clues as to their meaning. However, I found myself explaining a lot of terms instead of actually ever using them. Normally I kick myself over poor word usage, but I then realize that some of the audience has a natural aversion to white papers.

    [size="3"]Mini outline:

    I. What Pointers Are.
    [nbsp][nbsp][nbsp][nbsp] 1. Memory locations
    II. How to Use pointers.
    [nbsp][nbsp][nbsp][nbsp] 1. Initialize
    [nbsp][nbsp][nbsp][nbsp] 2. Reference Operator &
    [nbsp][nbsp][nbsp][nbsp] 3. Dereference operator *
    [nbsp][nbsp][nbsp][nbsp] 4. Member Access Operator ->
    [nbsp][nbsp][nbsp][nbsp] 5. Passing References to Functions
    III. Arrays.
    [nbsp][nbsp][nbsp][nbsp] 1. Pointer Arithmetic
    [nbsp][nbsp][nbsp][nbsp] 2. Rotate Array Example
    IV. The Void Pointer.
    V. Pointers to functions.
    [nbsp][nbsp][nbsp][nbsp] 1. Typedef
    [nbsp][nbsp][nbsp][nbsp] 2. Assign
    [nbsp][nbsp][nbsp][nbsp] 3. Class Member Functions
    [nbsp][nbsp][nbsp][nbsp] 4. Table Messaging System Example


    [size="5"]I. What Pointers Are:

    A pointer points to a location in memory where your data or algorithms are stored. That location is also called an address. Since we primarily work on 32-bit systems, memory addresses are 32-bits long or 4 bytes. This means that no matter what type item your pointer points to, the length of that pointer is 4 bytes.


    [size="5"]II. How to Use pointers.

    Pointers can point to any type. A type specifies the kind of data that a variable can store, such as int, long, double, or custom types that you define yourself such as structures or classes. To declare a variable as a pointer, first specify the type followed by an asterisk and then the variable name:

    type *variable_name; //asterisk touches variable name
    type* variable_name; //asterisk touches type
    The syntax used in both of these examples is correct. Notice in the following example the pointers are declared on the same line separated by a comma, but both names have the asterisk touching them. This is to ensure that they both get typed as pointers to the int type.

    int main()
    {
    int a = 10;
    int b = 20;
    int *pa, *pb;
    pa = &a
    pb = &b
    return 0;
    }
    If you don't include the asterisk in front of each pointer (i.e. if you declared them using [font="Courier New"][color="#000080"]int *pa, pb;[/color][/font], the line [font="Courier New"][color="#000080"]pb = &b[/color][/font] would produce an error such as "error C2440: '=' : cannot convert from 'int *' to 'int' This conversion requires a reinterpret_cast, a C-style cast or function-style cast."

    [size="3"] Initialize Pointer

    Before you can use a pointer, you have to initialize it. You must set it to point to a valid memory location, otherwise it points to nowhere and that can crash your computer. There are two ways to get a valid memory address. One way is to allocate memory and assign the pointer to it, but that is not a topic that we cover here. The other way is to create an ordinary variable and have the pointer point to that variable.

    [size="3"]Reference Operator &

    The "reference operator" or "address-of operator" exposes the memory address of the variable to which it is prefixed. The '&' operator specifies that the memory address of the variable should be retrieved and assigned to the pointer, not the contents of the variable. This distinction is crucial.

    [size="3"]Dereference Operator *

    We assigned a reference or address of a variable to be stored in a pointer using the '&' operator. Let's say we want to change the contents of the memory that the pointer points to:

    int hello = 0;

    int* pointer_to_hello = &hello //The & prefix yields the handle or pointer to that variable
    (*pointer_to_hello) = 25; //Dereference the pointer
    This example demonstrates dereferencing the pointer. The compiler knows what type of item the pointer points to, in this case an int, and therefore knows what value and how many bytes to write at that address. Dereferencing is accomplished by prefixing the asterisk operator on the pointer.

    [size="3"]Passing References to Functions

    Here's a more practical application of using a pointer:

    int main(void)
    {
    int hello = 0;
    SomeFunction(&hello);
    return 0;
    }

    void SomeFunction(int* value)
    {
    (*value) ++;
    }
    A pointer, or reference, to our variable was passed to the function and the function wrote to that address. This approach is particularly useful for large variables, such as structures, in that you don't have to copy those variables in their entirety to the function and then copy them back in order to return them. It is also well suited for use with functions that need to return several variables at once.

    Here's an example of a function that returns values for more than one variable. Since C++ only supports returning one value at a time, we pass a reference to the second variable that we want to fill and then dereference it inside the function.

    int Difference( int a, int b, bool* isNegative)
    {
    *isNegative = false; // dereference isNegative in order to set the value stored
    int retvalue = 0; // at that address to false in case our if conditional fails

    retvalue = a - b;

    if(retvalue < 0) //If true we change the bool value stored at the address
    *isNegative = true; //pointed to by isNegative to true

    return retvalue; //return the difference
    }
    [size="3"]Member Access Operator ->

    If you do have a pointer to a class or struct, you can access its data members one of two ways. The first way is to dereference the pointer and use the dot operator as if the pointer was an ordinary variable of the structure's type. The second way is to use the '->' operator. This allows you to access the variable directly as if you had dereferenced it.

    struct teststruct
    {
    int number;
    string hello[40];
    };

    teststruct hello;
    teststruct* pointer = &hello

    (*pointer).number = 11; //First way by dereferencing and using the dot operator
    pointer->hello = "This is a value being assigned to the string"; //Second way using -> operator

    int* pointer_number = &pointer->number; //retrieves handle to number
    *pointer_number = 25; //hello.number and pointer->number now equal 25

    [size="5"]III. Arrays.

    Arrays and pointers are closely related. The following is a brief review of arrays leading into pointer arithmetic.

    An array is a list of variables of the same type grouped together in one block of memory. The size of the memory block is the number of variables in the array multiplied by the size of the variable type. Size refers to the number of bytes. To access a variable in an array list, a subscript [] operator is used. Placing a number inside the subscript operator and appending it to the end of the array's name serves as a way to access the variable at that position in that array. The compiler uses the size of the variable as the footprint when stepping through the list to get to a variable location.

    The compiler uses the size of the variable when stepping through the list to get to a variable location. The size of the variable is sometimes referred to as its "footprint". The number used in the subscript is sometimes referred to as an ordinal.

    The subscript value is multiplied by the footprint size and the program seeks that far (that many bytes) into the memory block to read the variable at that location (address). Specifying a zero in the subscript would tell the program to read zero bytes into the memory block, considering that anything multiplied by zero is zero. Arrays are zero-based. So the first element would be located at the 0th location, or ordinal. Also note that the last ordinal in the array is one less than the array size it was created with. For instance, if an int array were created with 5 elements, the last ordinal would be 4 and would store the 5th element.

    int array[5];
    array[0]; //first element
    array[4]; //last element is stored in the 4th ordinal
    Arrays and pointer have an intimate relationship. Using the name of the array without the subscript [] operator serves as a pointer to the beginning of the memory block where the list of variables that constitute the array is stored.

    Because the variable name used by itself is a pointer to the first element in the array, you can assign it to a regular pointer.

    int array[10];
    int* pointer = array; //points to array[0]
    [size="3"]Pointer Arithmetic

    Pointer arithmetic provides an alternative method for stepping through memory in intervals the size of the type of the pointer. We assigned the pointer to the first element of the array to our pointer of type int. If we increment the pointer we will be pointing to the second element of the array.

    int array[10];
    int* pointer = array; //Points to array[0];

    (pointer + 1); //Points to the 2nd element - &array[1]

    pointer[1]; //Access to the 2nd element - array[1]

    pointer++; //increments the pointer to point to array[1];

    (array + 2); //Points to the 3rd element - &array[2]
    [size="3"]Rotate Array Example

    Pointer arithmetic allows us to perform operations on the array without actually moving the data around. We make adjustments to pointers instead. This is extremely useful for large data structures and large systems where system performance is critical.

    In this example, an array is created of 5 int types. Another array of pointers to the int type is also created to store the addresses of the ordinals of the first array. The ordinal addresses are stored in the array of int pointers in the order of n + 1; n being the ordinal the value sits at in the original array and n + 1 being the ordinal or the second array. After the rotation, the first reference address is appended to the end of the array of pointers.

    int array[5] = {1,2,3,4,5}; //initialize array of int values
    int* array2[5]; //create an array of pointers to int values
    int* pointer = array; //store the pointer to the first element of the array of int values

    for(int i = 0; i < 5; i++)
    {
    array2 = ++pointer; // increase the pointer to point to the next address in the array
    }

    array2[4] = array; //append the address of first ordinal to end of array of addresses
    Output of this first rotation would be:
    Array: {1,2,3,4,5}
    Dereference Array2: {2,3,4,5,1}

    This first iteration rotates the array by one while initializing the second array of pointers only. Rotations after this first initialization would be done solely to the array of pointers.

    pointer = array2[0]; //Store the value of the first ordinal of the array of pointers to 'int's

    for(int t = 0; t < 4; t++)
    {
    array2[t] = array2[t + 1]; //Cycle through and shift the addresses towards zero
    }

    array2[4] = pointer; //append the first address to the end of the array of pointers
    Note: Even after the two rotations, the original array still has its values in the original order. However, after two rotations, the array of pointers is two steps ahead of the original array. If the values of the first array were dumped, they would be in the order of {1,2,3,4,5}. If the pointers were dereferenced in the array of pointers, the values would be in the order of {3,4,5,1,2}.


    [size="5"]IV. The Void Pointer.

    Pointers can be set to point to any type. Pointers can even point to a Void type. The void type has a memory footprint of size zero. Before you can use a void pointer, you have to recast it into the type that it points to.

    void* voidpointer;
    int hello = 0;

    voidpointer = (void*) &hello //interpret the int pointer as a void pointer

    int* pointer;

    pointer = (int*) voidpointer; //Reinterpret as int pointer
    This is useful for programming a generic system that passes pointers whose types are not determined until they are to be used. A good example is a messaging system.

    struct MessageStruct
    {
    int Flag;
    void* pointer;
    }
    The void pointer would be reinterpreted into the right type by the program depending on the flag. (Example later)


    [size="5"]V. Pointers to functions

    Data isn't the only thing you can have a pointer to. You can also have a pointer to a function. Function pointers allow you to use functions during run time that you either didn't have available during development or that you aren't sure of the order in which the functions should be called.

    [size="3"]Typedef

    Function pointers are a bit more complicated in that they aren't a predefined type. They don't have a predefined size nor do they have a specific set of inputs or outputs. So before we can cast a pointer as a function type, we have to declare the function inputs and outputs. We do this using typedef:

    typedef int (*Function_Pointer_Type)(int);
    Notice the asterisk in front of the variable type name Function_Pointer. This means that whatever we cast with this new type, it is already a pointer. The inputs and outputs of this function are 'int's. These inputs and outputs are sometimes referred to as a function's "signature". Here is an example of a function that has the same signature as the above function type:

    int Square(int num)
    {
    return num * num;
    }
    [size="3"]Assign

    The name of the function itself gives us a pointer to it. All we have to do is leave off the parenthesis ()'s like so:

    Funtion_Pointer_Type function_pointer; //Already a pointer, remember?
    function_pointer = Square; //Leave off the ()'s and it's a pointer to the function

    int output = function_pointer(4); //use the pointer like the function!
    [size="3"]Class Member Functions

    You can also get pointers to class member functions this way too. However, in order to get a pointer to those functions, they must be declared static in the class definition. Unfortunately, declaring class member functions static prevents accessing the other class members.

    class foo
    {
    public:
    foo(); //Construct is private so no one outside the class can call it
    static int Square(int num); //Static class member functions can pass pointers
    int Add(int num); //Non static member functions will not give out it's pointer
    }

    foo::foo()
    {
    }

    int foo::Square(int num)
    {
    return num * num;
    }

    int foo::Add(int num)
    {
    return num + 5;
    }

    foo* fooclass; //declare a class of type foo
    Funtion_Pointer_Type function_pointerA, function_pointerB;
    function_pointerA = fooclass.Square; //no problem
    function_pointerB = fooclass.Add; //will not compile, function must be static
    [size="3"]Table Messaging System Example:

    The following example shows how you can use a combination of function pointers and void pointers to create a simple messaging system. The function pointers are stored in a table that this messaging system uses to know which function to call:

    struct MessageStruct
    {
    int flag;
    void* parameter;
    };

    enum{
    FLAG1, // = 0
    FLAG2,
    FLAG3,
    ...
    FLAG12, // = 12
    };

    void function1(void* input)
    {
    int* number = (int*) input; //recast void pointer as int pointer
    //do something
    }
    ...
    void function12(void* input)
    {
    char* text = (char*)input; //recast as char*
    text[0] = 'a'; //We can use the pointer like an array
    text[1] = 'b';
    //do something
    }

    typedef void (*Function_Pointer_Type)(void*) //type the function

    Function_Pointer_Type FunctionTable[12]; //make a global array of function-pointer type

    void LoadFunctionTable(void)
    {
    FunctionTable[0] = function1; //load the table with pointers to functions
    FunctionTable[1] = function2;
    ...
    FunctionTable[11] = function12;
    }

    void ProcessMessage(MessageStruct message)
    {
    FunctionTable[message.flag](message.parameter);
    }
    In the last function, we use the flag to look up the ordinal of the correct function and the parameter as the generic pointer. When the function gets the void pointer, it recasts it into the type for that function. Using this table looks a little something like this:

    //init variables
    MessageStruct Message;
    int number = 300;

    //Load Message Struct
    Message.flag = FLAG5;
    Message.parameter = (void*) &number

    //ProcessMessage
    ProcessMessage(Message);

    [size="5"]Conclusion

    Author:
    This concludes the article. Please reread it until you are clear on every line of code. I apologize in advance for any mistakes and I welcome any corrections and suggestions. Simply comment on this article in the forums (should be a link on this page) and the editor or I will make the necessary corrections.

    [size="3"] Credits:

    Mike Caetano AKA LessBread @ www.gamedev.net helped out with the technical accuracy, helped eliminate distractions, and mentioned some things that that were omitted.

    Ignacio Liverotti @ www.gamedev.net helped test the code and suggested a better convention on certain issues.

    "How Pointers Really Work" by OutAxDx @ www.gamedev.net. His article provided an idea on how to start this article.

    C++ Black Book by Steve Holzner. A Great reference manual for the C++ language.
Cancel Save
0 Likes 1 Comments

Comments

spam01@wi.rr.com
CORRECTION: void pointers do not have a memory size of zero. How could they exist in the computer if they did? Their type size is the size of a memory address. For 32-bit systems, this is almost certain to be a 4-byte integer type.
August 20, 2011 09:05 PM
You must log in to join the conversation.
Don't have a GameDev.net account? Sign up!

The purpose of this article is explain to a beginning or intermediate programmer what pointers are and to provide a few examples of how they are used.

Advertisement
Advertisement
Advertisement