Pointers
Chris Wild
1 Pointers
1.1 Description
-
Pointers are important because:
-
Allow access to objects created “on the fly” (dynamic storage allocation)
-
Permit access to “large” objects without copying
-
Allow selective access to parts of large structure
-
A pointer is the “name” or “address” of an object NOT the object itself.
-
Most variables have names assigned to them by the programmer at the time the program is written (e.g. “int numberOfCourses;”)
-
Every variable in a program has an machine address where that variable is stored in the main memory of the computer. This is the “machine’s name” for the variable.
-
Every variable in a program has a value which is the data stored in the variable’s address
-
integer is a variable whose value is an integer number
-
An integer pointer variable is a variable whose value is the address of an integer variable
-
The address-of operator (‘&’) can be used to get the address of any variable.
-
You declare a pointer variable by adding the pointer modifier (’*’) before the variable name.
-
You can get the object pointed at by a pointer variable by using the dereferencing operator (’*’) before the variable name.
1.2 Example
int *P, *Q; // P and Q are of type "pointer to integer"
int X; // X is of type "integer";
// Here is a "picture" of memory after these two definitions
// are done.
// "?" means the value is "undefined"
P = &X; // The address-of operator sets the value of P to
// the address of X
// In the following picture, an arrow is used in place of the
// machine address.
// The arrow means that the value stored in variable P is the
// machine address of X. Thins value can be used to access
// variable X.
//
// Now there are two ways to access the value of "X"
// 1) Use the programmer assigned name ("X")
// 2) Use the pointer value (the dereferencing operator gets
// the value pointed to, which is the value of "X"
*P = 6;
// "*P" is the value of variable X, hence this sets X to 6
// another way to do the same thing is
// X = 6;
// To print out the value of X you could use either of the following two statements
cout << X;
cout << *P;
//You can use the address-of operator to initialize a pointer (as shown above).
// Or you can use the value of another pointer. Shown below:
Q = P;
// now both Q and P have the same value - the address of X.
// Thus it is said that both Q and P point at the same variable.
1.3 Tips
-
An integer and a pointer to an integer are two different types of objects. Both can have a programmer assigned name. Both have a machine address. Both have values. However the values have different meanings. The value of an integer variable is a whole number. The value of an integer pointer is an address of an integer variable (a completely different second object). The same is true for other data types as well - including those you define yourself.
-
NOTE: the ’*’ operator serves two purposes with pointers, 1) to identify that a variable is a pointer 2) to get the object pointed at by a pointer (dereferencing)
-
It is possible to misuse the address-of an dereferencing operators. All of the following statements are wrong
*P = &X; // *P is of type "integer", "&X" is of type pointer to integer
Q = *P: // *P is of type integer, Q is of type pointer to integer
Q = &P; // &P is of type pointer to pointer to integer but Q is just of type pointer to integer
2 new and delete
2.1 Description
-
Dynamically allocated memory is controlled through the two operators new and delete
-
The new operator allocates sufficient memory to store one or more objects of the specified type.
-
The type of the object to be allocated follows the new operator
-
Memory is allocated from an area reserved for new objects. ( a memory pool)
-
The delete operator returns the memory allocated to an object back to the memory pool to be reused.
-
Since dynamically allocated objects are unknown at the time you write the program, they are accessed through pointers.
-
In effect the pointer is the “name” of the dynamically allocate object (at least temporarily. Since a pointer can refer to several objects in the same program, that name is reused.
-
There are several \hyperlink{ptrerrors}{misuses} of dynamic memory that the programmer should be aware.
2.2 Example
int *P = NULL; // define an initially empty pointer
P = new int; // allocates memory for a new integer and assigns its location to "P"
delete P; // returns the memory allocated by the new integer back to the memory pool
P = new int[100]; // allocates a array of 100 integers and assigns its location to "P"
delete [ ] P; // returns the entire array to the memory pool
2.3 Tips
- Always initializes a pointer when it is defined (use NULL you don’t otherwise no the address)
int * P = NULL;
- always set a pointer to NULL (or
0
) after you delete the object it is pointing at
P = new int; // another new integer
delete P;
P = NULL;
-
Always delete an object you don’t need anymore.
-
Never let a dynamically allocated object slip through your fingers - always have some pointer pointing at it.
3 Dangling Pointers and Memory Leaks
3.1 Description
Because pointers provide access a memory location and because data and executable code exist in memory together, misuses of pointers can lead to both~ bizarre effects and very subtle errors.
The main problems with pointers are uninitialized pointers, memory leaks and dangling pointers.
-
Uninitialized pointers pose the greatest threat, since the value stored in an uninitialized pointer could be randomly pointing anywhere in memory. Storing a value using an uninitialized pointer has the potential to overwrite anything in your program, including your program itself
-
Pointers should always be initialized when they are defined. The special value NULL (or ‘
0
’) is reserved for pointers which are currently not pointing anywhere in particular.
-
-
Memory leaks occur when a dynamically allocated variable (which must be allocated through a pointer), is no longer pointed to by that or any other pointer and that memory has not be explicitly deleted
-
Memory leaks pose no immediate problem but eventually may clog up memory leading to an out-of-memory fault
-
-
Dangling pointers refer to a pointer which was pointing at an object that has been deleted. The pointer still has the address of the object even though the object no longer exists. The compiler will not catch such errors and possibly neither will the run-time system. If the memory allocated to the now deleted object is given to another object, then the pointer has the address (illegally) of the new object and can be used to access and change that object – even if it is an object of the wrong type.
3.2 Example
int *P; // P is uninitialized - could be pointing anywhere
*P = 3; // Use of uninitialized pointer - could cause core dump, illegal access to memory
// or worse an unexpected change to a random spot in memory
P = new int; // allocate a new integer and assigns a pointer to "P"
P = NULL: // the new integer is now orphaned - nobody can get to it
// this is amemory leak
P = new int; // another new integer
delete P; // which is correctlly returned to the system
// but now P is a _dangling_pointer_ since it still points to
// the now deleted integer
3.3 Tips
- Always initialize a pointer when it is defined (use NULL you don’t otherwise know the address)
int *P = NULL;
- Always set a pointer to NULL after you delete the object it is pointing at.
P = new int; // another new integer
<:\smvdots{}:>
delete P;
P = NULL; // otherwise P would be a dangling pointer
-
Always delete an object you don’t need anymore.
-
Never let a dynamically allocated object slip through your fingers – always have some pointer pointing at it.
4 The Relationship Between Pointers and Arrays
4.1 Description
-
We have already pointed out that arrays need special care in C++.
-
Most of the differences between arrays and the basic built in type can be understood once you realize that an array name is really just a pointer to a block of memory containing the elements in the array.
-
Because elements in an array are located in the same block of memory, access to the array can be done by using arithmetic on the pointer. For instance, the first element of the array is at the address pointed to by the pointer. To get the second element you add the size of the first element to the array base pointer to get a pointer to the second element and so on.
This helps explain why the first element in the array is at index 0 -- because its offset from the base array address is 0.
-
Because of the relationship between pointers and arrays, you can mix pointer notation and operators and array notation and operators.
-
Passing an array into a function is really only passing a pointer to the array. Hence arrays look like “call by reference”, unlike the basic types, but it’s really a “call by value” of a pointer.
4.2 Example
const int ARRAY_SIZE = 50;
float A[ARRAY_SIZE];
-
*A is equivalent to A[0]
-
*(A+1) is equivalent to A[1]
-
*(A + i) is equivalent to A[ i ]
float *ptr; // declare a pointer to a floating point number
ptr = &A[25]; // point near the middle of array A
-
ptr[0] is equivalent to A[25]
-
ptr[1] is equivalent to A[26]
-
ptr[10] is equivalent to A[35]
-
ptr[-1] is equivalent to A[24]] // NOTE you can use negative indices
-
ptr[-25] is equivalent to A[0]
-
ptr[-26] is a syntactically legal C++ statement but is a logical error since it points to memory not allocated to array “A”.
4.3 Tips
- Avoid using pointer arithmetic when array notation is more appropriate.