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.
[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
int main()
{
int a = 10;
int b = 20;
int *pa, *pb;
pa = &a
pb = &b
return 0;
}
[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
[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) ++;
}
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
}
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
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]
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]
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
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
[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
struct MessageStruct
{
int Flag;
void* pointer;
}
[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);
int Square(int num)
{
return num * num;
}
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!
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
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);
}
//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.