Pointers are one of the most powerful and confusing aspects of the C language. A pointer is a variable that holds the address of another variable. To declare a pointer, we use an asterisk between the data type and the variable name:


int *pnPtr; // a pointer to an integer value
double *pdPtr; // a pointer to a double value

int* pnPtr2; // also valid syntax
int * pnPtr3; // also valid syntax

Note that an asterisk placed between the data type and the variable name means the variable is being declared as a pointer. In this context, the asterisk is not a multiplication. It does not matter if the asterisk is placed next to the data type, the variable name, or in the middle — different programmers prefer different styles, and one is not inherently better than the other.

Since pointers only hold addresses, when we assign a value to a pointer, the value has to be an address. To get the address of a variable, we can use the address-of operator (&):


int nValue = 5;
int *pnPtr = &nValue; // assign address of nValue to pnPtr

Conceptually, you can think of the above snippet like this:

It is also easy to see using code:


int nValue = 5;
int *pnPtr = &nValue; // assign address of nValue to pnPtr

cout << &nValue << endl; // print the address of variable nValue
cout << pnPtr << endl; // print the address that pnPtr is holding

On the author’s machine, this printed:

0012FF7C
0012FF7C

The type of the pointer has to match the type of the variable being pointed to:


int nValue = 5;
double dValue = 7.0;

int *pnPtr = &nValue; // ok
double *pdPtr = &dValue; // ok
pnPtr = &dValue; // wrong -- int pointer can not point to double value
pdPtr = &nValue; // wrong -- double pointer can not point to int value

Dereferencing pointers

The other operator that is commonly used with pointers is the dereference operator (*). A dereferenced pointer evaluates to the contents of the address it is pointing to.


int nValue = 5;
cout << &nValue; // prints address of nValue
cout << nValue; // prints contents of nValue

int *pnPtr = &nValue; // pnPtr points to nValue
cout << pnPtr; // prints address held in pnPtr, which is &nValue
cout << *pnPtr; // prints contents pointed to by pnPtr, which is contents of nValue

The above program prints:

0012FF7C
5
0012FF7C
5

In other words, when pnPtr is assigned to &nValue:
pnPtr is the same as &nValue
*pnPtr is the same as nValue

Because *pnPtr is the same as nValue, you can assign values to it just as if it were nValue! The following program prints 7:


int nValue = 5;
int *pnPtr = &nValue; // pnPtr points to nValue

*pnPtr = 7; // *pnPtr is the same as nValue, which is assigned 7
cout << nValue; // prints 7

Pointers can also be assigned and reassigned:


int nValue1 = 5;
int nValue2 = 7;

int *pnPtr;

pnPtr = &nValue1; // pnPtr points to nValue1
cout << *pnPtr; // prints 5

pnPtr = &nValue2; // pnPtr now points to nValue2
cout << *pnPtr; // prints 7

The null pointer

Sometimes it is useful to make our pointers point to nothing. This is called a null pointer. We assign a pointer a null value by setting it to address 0:


int *pnPtr;
pnPtr = 0; // assign address 0 to pnPtr

or shorthand:


int *pnPtr = 0;  // assign address 0 to pnPtr

Note that in the last example, the * is not a dereference operator. It is a pointer declaration. Thus we are assigning address 0 to pnPtr, not the value 0 to the variable that pnPtr points to.

C (but not C++) also defines a special preprocessor define called NULL that evaluates to 0. Even though this is not technically part of C++, it’s usage is common enough that it will work in every C++ compiler:


int *pnPtr = NULL; // assign address 0 to pnPtr

Because null pointers point to 0, they can be used inside conditionals:


if (pnPtr)
    cout << "pnPtr is pointing to an integer.";
else
    cout << "pnPtr is a null pointer.";

Null pointers are mostly used with dynamic memory allocation, which we will talk about in a few lessons.

The size of pointers

The size of a pointer is dependent upon the architecture of the computer — a 32-bit computer uses 32-bit memory addresses — consequently, a pointer on a 32-bit machine is 32 bits (4 bytes). On a 64-bit machine, a pointer would be 64 bits (8 bytes). Note that this is true regardless of what is being pointed to:


char *pchValue; // chars are 1 byte
int *pnValue; // ints are usually 4 bytes
struct Something
{
    int nX, nY, nZ;
};
Something *psValue; // Something is probably 12 bytes

cout << sizeof(pchValue) << endl; // prints 4
cout << sizeof(pnValue) << endl; // prints 4
cout << sizeof(psValue) << endl; // prints 4

As you can see, the size of the pointer is always the same. This is because a pointer is just a memory address, and the number of bits needed to access a memory address on a given machine is always constant.

Quiz

1) What values does this program print? Assume a short is 2 bytes, and a 32-bit machine


short nValue = 7; // &nValue = 0012FF60
short nOtherValue = 3; // &nOtherValue = 0012FF54
short *pnPtr = &nValue;

cout << &nValue << endl;
cout << nValue << endl;
cout << pnPtr << endl;
cout << *pnPtr << endl;
cout << endl;

*pnPtr = 9;

cout << &nValue << endl;
cout << nValue << endl;
cout << pnPtr << endl;
cout << *pnPtr << endl;
cout << endl;

pnPtr = &nOtherValue;

cout << &nOtherValue << endl;
cout << nOtherValue << endl;
cout << pnPtr << endl;
cout << *pnPtr << endl;
cout << endl;

cout << sizeof(pnPtr) << endl;
cout << sizeof(*pnPtr) << endl;

Quiz solutions

1) Show Solution

6.8 — Pointers, arrays, and pointer arithmetic
Index
6.6 — C-style strings