Home | API | MFC | C++ | C | Previous | Next

Programming With C++

Pointers and References

Pointers

A pointer is a variable that stores a memory address. Pointers provide the capability to manipulate computer memory directly. Since a non-generic pointer directly references another variable value it is necessary to specify which data type a pointer is going to point to. This is known as the base type.

A general pointer variable is declared as follows –

type *var-name;

The base type is important because the compiler needs to know how many bytes make up the value pointed to. Pointers can have any name that is legal for other variables however standard naming convention is to start each pointer name with p and capitalise the second letter, as in pAge or pHeap.

Initialising a Pointer

When created, all pointers should be initialised. A pointer that is not initialised is called a wild pointer and will contain a random or junk value. Wild pointers are dangerous because they cause a program to access invalid memory locations leading to unpredictable results when the pointer is used.

To initialise the pointer use the nullptr. In earlier versions of C++, a pointer was assigned a null value when created by using either 0 or NULL as the value however due to ambiguities with function overloading the nullptr keyword is now preferred. Examples of poiner initialisation are listed below-

int *pPointer = 0;
int *pPointer = NULL;
int *pPointer = nullptr; (Superceeds the above two methods of initialisation)

Setting a Pointer to store a Variable Address

To store the address of a variable in a pointer use the referencing operator &.  The referencing operator, when placed in front of a variable, will always return the address of that variable.

int a_value = 30;  (declares int variable to value 30)
int *pTr= &a_value; (declares pointer name pTr of type int and sets it to address of variable a_value)

Assigning and Accessing Values

The indirection operator or dereferencing operator (*) operates on a pointer, and returns the value stored in the address kept in the pointer variable. For example, if pTr is an int pointer and the address contains 30, using *pTr returns that int value. The dereferencing operators can also be used to assign a new value to an address. Using *pTr = 101 will assign the value of 101 to the address stored at pointer pTr.

Pointer Arithmetic

There are only four arithmetic operators that can be used on pointers: ++, – –, +, and –

An increment or decrement operation on a pointer will point to the next value in the block of memory. The new location will represent the location of the next variable value and not the next byte of memory. For instance, using the ++ on an int pointer is telling the compiler that you want it to point to the next consecutive integer. Decrementing pointers ( -- ) have the opposite effect. The address contained in the pointer is incremented or decremented by the size of the type being pointed to. This ensures that the pointer only points to the beginning of some valid data and not to some arbitrary memory location. In the case of character pointers, an increment or decrement will only change the pointer value by 1 since characters are one byte long. Every other type of pointer will increase or decrease by the length of its base type.

Integer values can also be added or subtracted to or from a pointer. For instance, adding 5 to a pointer value means pointing to the 6th element beyond the pointer current address. In the code section below, a pointer is used to display the contents of an int array together with the address of each element-

#include <iostream> int main()
{
int myvalues[10]={1,2,3,4,5,6,7,8,9,10};//declare and initialise array of ints
int *iPtr=nullptr;//declare pointer and set to null
iPtr=myvalues;//set pointer to address of first element in array
std::cout << "The size of an int " << sizeof(int)<< "\n";
for (int aNum : myvalues) // range based for
{
std::cout << "The array elements are " << *iPtr <<" address "<<std::dec << iPtr<<"\n";
iPtr++;//increase pointer
}
}

Pointer Comparisons

Pointers can also be compared by using relational operators, such as ==, <, and >. however,the two pointers must be of the same type. Two pointers of the same type are equal if they are both null, point to the same function, or represent the same address

Using the Const Keyword on Pointers

If the address contained in the pointer is declared constant then it cannot be changed, however, the data at that address can be changed:

int valueofint = 1;
int* const pTr = &valueofint;
*pTr = 10;  (Data pointed to is changed)
int valueofint1 = 21;
*pTr=&valueofint1  (not allowed because pointer address cannot be changed)

If the Data pointed to is declared as constant then cannot be changed, but the address contained in the pointer can be changed

int valueofint = 1;
const int*pTr = &valueofint;
*pTr = 10; (not allowed because value pointed to cannot be changed)
int valueofint1 = 21;
*pTr=valueofint1 

If both the address contained in the pointer and the value being pointed are declared constant neither can be changed.

int valueofint = 1;
const int* const pTr = &valueofint;
*pTr = 10; (not allowed because value pointed to cannot be changed)
int valueofint1 = 21;
*pTr=valueofint1(not allowed because pointer address cannot be changed)

Consts are particularly useful when passing pointers to functions. If function parameters are declared with the most restrictive access possible then a function cannot modify those values . This will keep programmers from making unwanted changes to pointer values or data.

Pointer to a Pointer

Normally, a pointer contains the address of a variable however when we define a pointer to a pointer, the first pointer contains the address of the second pointer, which points to the location that contains the actual value. A variable that is a pointer to a pointer is declared by placing an additional asterisk in front of its name

int **var;

Accessing the target value requires that the asterisk operator be applied twice

#include <iostream>
int main()
{
int i=10;
int *pInt=&i;
int **pPtr=&pInt;
int ***ppPtr=&pPtr;
/*output size of pointer to pointer*/
std::cout << "the size of ppPtr " << sizeof(ppPtr)<<"\n";
/*dereference pInt and show address*/
std::cout << "The integer value is " << *pInt <<" address of pointer"<< pInt<<"\n";
/*dereference pPtr and show address*/
std::cout << "The integer value is " << **pPtr <<" address "<< pPtr<<"\n";
/*dereference ppPtr and show address*/
std::cout << "The integer value is " << ***ppPtr <<" address "<< ppPtr<<"\n";
}

Void Pointers

The void pointer or generic pointer is a special type of pointer that has no associated data type. A void pointer is declared like a normal pointer but the void keyword is used to declare the pointer’s type.

void *pTr; // ptr is a void pointer

Since a void pointer does not know what type of object it is pointing to, it cannot be dereferenced without explicitly casting it to the base type before dereferencing. The code section below illustrates how to implement and dereference a void pointer. Note any attempt to print a value of the pVoid pointer will generate an error - 

#include <iostream>
int main()
{
int i=10;
void *pVoid=&i;/*declare void pointer*/
int *pInt = static_cast<int*>(pVoid);/*cast void pointer*/
std::cout<< "The integer value is " << *pInt <<" address "<< pInt<<"\n";/*output ponter value*/
}
}


Pointer arithmetic is not possible on a void pointers since the pointer is of an unknown base type and hence the compiler has no idea of its size in memory. In general, it is a good idea to avoid using void pointers as they bypass compiler type checking. Void pointers are used to implement generic functions.

Why Use Pointers?

Pointers are useful because they allow the passing of data structures to functions more efficiently. Since there is no duplication of data, passing by direct reference offers an increase in overall efficiency especially in terms of speed and memory usage. Pointers also allow management of dynamically allocated memory.

Potential Problems with Pointers 

Since the misuse of pointers can be a major source of bugs,security vulnerabilities and can make code unnecessarily complex, the use of pointers is best avoided in C++ where possible. Some of the potential problems with careless use of pointer are - 

References

A reference variable is an alias for an existing variable.  A reference, like a pointer, is also implemented by storing the address of an object however a reference differs from a pointer it that cannot be null, cannot be reassigned, does not allow arithmetic operation, does not allow redirection, and  shares the same memory address with the original variable

A reference is declared using the reference operator ( & )

#include <iostream>
int main()
{
int i=10;
int &r=i;//create reference and set to address of variable i
std::cout << "value of variable i " << i<<"\n";// output value of variable i
std::cout << "value of reference r " << r<<"\n";//output value of ref f
r=20;//changing value of i using alias
std::cout << "new value of variable i " << i<<"\n";// output new value of variable i
}

Using Keyword const on References

One of the major advantages of references is that they allow a called function to work on parameters that have not been copied from the calling function. As the called function works using parameters directly it is often important to ensure that the called function cannot change the value of the original variable. A const reference parameter prevents any attempts at assigning a new value.

const int& constReference = originalvalue ;


Home | API | MFC | C++ | C | Previous | Next
The Basics | Variables and Constants | Arrays | C-strings | Expressions and Operators | Controlling Program Flow | C++ Functions | Pointers and References | Memory Map and Free Store | Smart Pointers | Classes | Structures | Inheritance | Polymorphism | Templates | The Standard Template Library | The STL String Class | Namespace | Type Conversions | Input and Output Streams | The C++ Preprocessor | Exception Handling

Last Updated: 15 September 2022