Pointer Basics and Pass-by-Address
 

What is a Pointer?

A pointer is a variable that stores a memory address.  Pointers are used to store the addresses of other variables or memory items.  Pointers are very useful for another type of parameter passing, usually referred to as Pass By Address.  Pointers are essential for dynamic memory allocation.

Declaring pointers:

Pointer declarations use the * operator.  They follow this format:
  typeName * variableName;

 int n;        // declaration of a variable n 
 int * p;      // declaration of a pointer, called p 

In the example above, p is a pointer, and its type will be specifically be referred to as "pointer to int", because it stores the address of an integer variable.

Note:  Sometimes the notation is confusing, because different textbooks place the * differently.  The three following declarations are equivalent:

 int *p; 
 int* p; 
 int * p; 

All three of these declare the variable p as a pointer to an int.

De-referencing pointers:

Once a pointer is declared, you can refer to the thing it points to by "dereferencing the pointer".
 int * p;

Now that p is declared as a pointer to an int, the variable p stores the address.  To dereference the pointer, use the * operator:

 cout << p;        // this will print out the address stored in p 
 cout << *p;       // this will print out the data being pointed to 

The notation *p now refers to the target of the pointer p.

Note:  The notation can be a little confusing. If you see the * in a declaration statement, a pointer is being declared for the first time. AFTER that, when you see the * on the pointer name, you are dereferencing the pointer to get to the target.

Pointers don't always have valid targets. If the pointer is storing a 0, for instance, this is known as the NULL pointer.  It never has a valid target.  If you try to dereference a pointer that does not have a valid target, your program will experience an error called a "segmentation fault" and will probably crash.  The null pointer is the only literal number you may assign to a pointer, since it is often used to initialize pointers or to be used as a signal. You may NOT assign arbitrary numbers to pointer variables:

 int * p = 0;    // okay.  assignment of null pointer to p 
 int * q; 
 q = 0;          // okay.  null pointer again. 
 int * z; 
 z = 900;        // BAD!  cannot assign other literals to pointers! 

Pointer types:

Although all pointers are addresses (and therefore represented similarly in data storage), we want the type of the pointer to indicate what is being pointed to.  Therefore, C++ treats pointers to different types AS different types themselves.
 int * ip; 
 char * cp; 
 double * dp; 

These three pointer variables (ip, dp, cp) are all considered to have different types, so assignment between any of them is illegal.  The automatic type coercions that work on regular numerical data types do not apply:

 ip = dp;        // ILLEGAL 
 dp = cp;        // ILLEGAL 
 ip = cp;        // ILLEGAL 

As with other data types, you can always force a coercion by re-casting the data type.  Be careful that you know what you are doing, though, if you do this!

 ip = static_cast<int *>(dp); 

The "address of" operator:

There is another use of the & operator.  We saw that when it is used in a declaration, it creates a reference variable. When the & is used in regular statements (not reference declarations), the operator means "address of". Example:
 int n; 
 cout << &n;    // this prints out &n, the "address of n". 

This operator is useful in attaching pointers to data items! Consider the following:

 int n;        // integer 
 int * p;      // pointer to an integer 

At this point, we don't know what p is pointing to. It might not even be pointing to a valid target at the moment! It contains some random value from memory right now, because we haven't initialized it. However, we can point p at n by using the & operator.

 p = &n;        // assigns the "address of n" to the pointer p 

Pass By Address:

We've seen Pass By Value and Pass By Reference.  If you declare a formal parameter of a function as a pointer type, you are passing that parameter by its address.  The pointer is copied, but not the data it points to.  So, Pass By Address offers another method of allowing us to change the original argument of a function (like with Pass By Reference).  Don't pass in the argument itself -- just pass in its address.

Example:

 void SquareByAddress(int * n) 
 {  *n = (*n) * (*n);  } 

 int main() 
 { 
   int num = 4; 
   cout << "Original = " << num << '\n'; 
   SquareByAddress(&num); 
   cout << "New value = " << num << '\n'; 
 } 

Pointers and Arrays:

When you declare an array normally, you get a pointer for free.  The name of the array acts as a pointer to the first element of the array.
 int list[10];   // the variable list is a pointer 
                 // to the first integer in the array 
 int * p;        // p is a pointer.  It has the same type as list. 
 p = list;       // legal assignment.  Both pointers to ints. 

In the above code, the address stored in list has been assigned to p.  Now both pointers point to the first element of the array.  Now, we could actually use p as the name of the array!

 list[3] = 10; 
 p[4] = 5; 
 cout << list[6]; 
 cout << p[6]; 

Pointer Arithmetic

Another useful feature of pointers is pointer arithmetic.  In the above array example, we referred to an array item with p[6].  We could also say *(p+6).  When you add to a pointer, you do not add the literal number.  You add that number of units, where a unit is the type being pointed to.  For instance, p + 6 in the above example means to move the pointer forward 6 integer addresses.  Then we can dereference it to get the data  *(p + 6).

What pointer arithmetic operations are allowed?

  • A pointer can be incremented (++) or decremented (--)
  • An integer may be added to a pointer (+ or +=)
  • An integer may be subtracted from a pointer (- or -=)
  • One pointer may be subtracted from another

  • Most often, pointer arithmetic is used in conjunction with arrays.

    Example:  Suppose ptr is a pointer to an integer, and ptr stores the address 1000.  Then the expression (ptr + 5) does not give 1005 (1000+5).  Instead, the pointer is moved 5 integers (ptr + (5 * size-of-an-int)).  So, if we have 4-byte integers, (ptr+5) is 1020 (1000 + 5*4).


    This code example illustrates the values stored in pointers and how the addresses line up for arrays:  pointers.cpp

    Pass By Address with arrays:

    The fact that an array's name is a pointer allows easy passing of arrays in and out of functions.  When we pass the array in by its name, we are passing the address of the first array element.   So, the expected parameter is a pointer.  Example:
    // This function receives two integer pointers, which can be names of integer arrays. 
       int Example1(int * p, int * q); 
    

    When an array is passed into a function (by its name), any changes made to the array elements do affect the original array, since only the array address is copied (not the array elements themselves).

     void Swap(int * list, int a, int b) 
     { 
       int temp = list[a]; 
       list[a] = list[b]; 
       list[b] = temp; 
     } 
    

    This Swap function allows an array to be passed in by its name only.  The pointer is copied but not the entire array.  So, when we swap the array elements, the changes are done on the original array.  Here is an example of the call from outside the function:

     int numList[5] = {2, 4, 6, 8, 10}; 
     Swap(numList, 1, 4);        // swaps items 1 and 4 
    

    Note that the Swap function prototype could also be written like this:

     void Swap(int list[], int a, int b);
    
    The array notation in the prototype does not change anything. An array passed into a function is always passed by address, since the array's name IS a variable that stores its address (i.e. a pointer).

    Pass-by-address can be done in returns as well -- we can return the address of an array.

     int * ChooseList(int * list1, int * list2) 
     { 
       if (list1[0] < list2[0]) 
         return list1; 
       else 
         return list2;    // returns a copy of the address of the array 
     } 
    

    And an example usage of this function:

     int numbers[5] = {1,2,3,4,5}; 
     int numList[3] = {3,5,7}; 
     int * p; 
     p = ChooseList(numbers, numList); 
    

    Here is a sample program that illustrates the differences between passing by value, reference, and address: pass.cpp.

    Const:

    The keyword const can be used in parameter passing for pointers in a similar way as for references. It is used for a similar situation -- it allows parameter passing without copying anything but an address, but protects against changing the data (for functions that should not change the original)

    The format:    const typeName * v
    This establishes v as a pointer to an object that cannot be changed through the pointer v.
    Note: This does not make v a constant! The pointer v can be changed. But, the target of v can never be changed.
    Example:

      int Function1(const int * list);  // the target of list can't 
                                        // be changed in the function 
    

    Note: The pointer can be made constant, too. Here are the different combinations:

    1) Non-constant pointer to non-constant data

      int * ptr;
    

    2) Non-constant pointer to constant data

      const int * ptr;
    

    3) Constant pointer to non-constant data

      int x = 5;
      int * const ptr = &x;   // must be initialized here 
    

    An array name is this type of pointer - a constant pointer (to non-constant data).

    4) Constant pointer to constant data

      int x = 5;
      const int * const ptr = & x;
    



    Code examples from Chapter 5