Your browser doesn't support JavaScript Pointers and References – Windows Programming

Pointers and References

Pointers

A pointer is a variable that stores the memory address of another object. Pointers provide the capability to access and manipulate objects indirectly through their memory addresses. A pointer has a type, known as its base type, which specifies the type of object it can point to.

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 legal variable name however, the standard naming convention is to start each pointer name with p and capitalize the second letter, as in pAge or pHeap.

Initialising a Pointer

When a pointer is declared without being initialised, its value is indeterminate. Using such a pointer can result in unpredictable behaviour, so pointers should normally be initialised before they are used.

The nullptr keyword is used to indicate that a pointer does not currently point to an object. In older versions of C++, 0 or NULL were commonly used for this purpose. These are still valid in appropriate contexts, but nullptr is preferred in modern C++ because it has a specific pointer type and avoids ambiguities that can occur with function overloading.

Examples of pointer initialisation are:. The third form is the preferred modern C++ approach.

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 address-of operator (&). When placed before a variable, the & operator produces the memory 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

Here, pTr is an int pointer and is assigned the address of a_value.

Assigning and Accessing Values

The indirection operator or dereferencing operator (*) operates on a pointer and returns the value stored in the address stored 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 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 four arithmetic operations that can be performed on pointers: increment (++), decrement (--), addition (+), and subtraction (-).

An increment or decrement operation on a pointer moves the pointer to the next or previous element of the type being pointed to. It does not simply move the pointer to the next or previous byte of memory. For instance, using ++ on an int pointer moves the pointer to the next int in an array. Decrementing a pointer (--) has the opposite effect.

The amount by which the pointer’s address changes depends on the size of the type being pointed to. For example, if an int occupies four bytes, incrementing an int pointer will normally increase its address by four bytes. In the case of a char pointer, the address increases or decreases by one byte because a char occupies one byte.

Pointer arithmetic is normally used when working with arrays. The resulting pointer must remain within the same array, or one position beyond the end of the array. A pointer that is moved outside these boundaries cannot safely be used to access an object.

Integer values can also be added to or subtracted from a pointer. For instance, adding 5 to a pointer value means pointing to the 6th element beyond the pointer’s 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 be compared using comparison operators such as ==, <, and >. When comparing pointers, the comparison determines the relationship between the addresses they contain. Two pointers compare equal when they both contain nullptr or when they point to the same object.

Using the const Keyword on Pointers

The const keyword can be used with pointers in different ways. It can make the pointer itself constant, make the data pointed to constant, or make both the pointer and the data constant.

If the pointer is declared as constant, the pointer cannot be changed to contain a different address, but the data at that address can be changed:

int valueofint = 1;
int* const pTr = &valueofint;

*pTr = 10;              // allowed because the value pointed to can be changed

int valueofint1 = 21;
pTr = &valueofint1;     // not allowed because the pointer is constant

If the data pointed to is declared as constant, the data cannot be changed through the pointer, but the pointer itself can be changed to point to a different address:

int valueofint = 1;
const int* pTr = &valueofint;

*pTr = 10;              // not allowed because the value pointed to is constant

int valueofint1 = 21;
pTr = &valueofint1;     // allowed because the pointer is not constant

If both the pointer and the data pointed to are declared as constant, neither can be changed:

int valueofint = 1;
const int* const pTr = &valueofint;

*pTr = 10;              // not allowed because the value pointed to is constant

int valueofint1 = 21;
pTr = &valueofint1;     // not allowed because the pointer is constant

In summary:

  • int* const pTr — constant pointer to an int
  • const int* pTr — pointer to a constant int
  • const int* const pTr — constant pointer to a constant int

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";
}

C++ allows multiple levels of pointers. Each additional level is represented by another asterisk (*).

Void Pointers

The void or generic pointer is a special pointer that does not specify the type of object it points to. 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 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 when a program needs to access an object indirectly through its memory address. They can be used to allow functions to modify objects supplied by the caller, to work with arrays and data structures, and to manage dynamically allocated memory.

Pointers can also be useful when working with dynamically created objects whose lifetime needs to extend beyond the scope in which they were created.

Potential Problems with Pointers 

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

  • uninitialised pointers – the value stored in an uninitialised pointer could be pointing to anywhere in memory.  Storing a value using an uninitialised pointer has the potential to overwrite anything in a program.  Therefore pointers should always be initialised
  • memory leaks – caused when pointers to a value allocated on the heap have been lost.
  • dangling pointers – a pointer pointing to an object that has been deleted.  Although the pointer still has the object address, the memory for that object will have been returned to the system.

References

A reference is an alias for an existing object. Once a reference has been initialised, it provides another name for that object.

Unlike a pointer, a reference must be initialised when it is created, cannot normally be changed to refer to a different object, and does not support pointer arithmetic. A reference also does not need to be dereferenced explicitly when accessing the object it refers to.

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 igo

Using Keyword Const on References

A reference parameter allows a function to work directly with the object supplied by the caller rather than receiving a separate copy of that object. Since it’s often important to ensure that the called function cannot change the original variable value, declaring a const reference parameter prevents any attempts at assigning a new value.

const int& constReference = originalvalue ;