CPSC170
Fundamentals of Computer Science II

Pre Lab

We have seen that when we declare an array, the name of the array is really a variable that holds the address for the first element of the array. Thus,

    int List[10];
  

actually allocates 11 words (4 bytes for each word on our lab computers.) of memory for the program: ten words for the ten elements of the array, and one word that holds the address of the first of the ten words of the array. Thus the variable, List is implicitly a pointer, i.e., a variable that itself isn't a data object, but points to a data object.

C++ provides a mechanism to explicitly declare variables that are pointers to data objects. The asterisk is the syntax used for such a declarations. For example,

    int * numPtr;
    Fraction * fractPtr;
  

declare two pointers: numPtr is a variable capable of pointing to an integer, and fractPtr is a variable capable of pointing to a Fraction object.

Note that with the above two declarations, the two pointer variables are not pointing to any valid integer or Fraction object. C++ provides a mechanism for a pointer variable to point to an existing data object using the ampersand syntax. For example,

    int aNum;
    Fraction aFract;

    numPtr = &aNum;
    fractPtr = &aFract;
  

assigns to numPtr the address of the variable aNum, and to fractPtr the address of the variable aFract.

It is sometimes useful to see the actual address of a variable in a program. If asked to print a pointer variable, the cout object of the iostream library will print the contents of the pointer variable, i.e., the address it holds, as an integer. These are often very big numbers, and so it is useful to print them out in hexadecimal. The keyword hex can be used to ask cout to print any integers in hexadecimal, until the dec is used. For example,

    cout << hex << numPtr << dec << aNum << endl;
  

will print the value of numPtr in hexadecimal, and the value of aNum in decimal.

The asterisk can also be used to dereference a pointer. Thus,

    *numPtr = 7;
  

will have the same effect as

    aNum = 7;
  

it is useful to use parentheses to disambiguate what is qualified by the asterisk, particularly when using member functions for objects of a user-defined class type. For example,

    aNum = (*fractPtr).getNumerator();
  

Without the parenthesis, it is unclear whether the asterisk is to be applied to fractPtr, or to fractPtr.getNumerator().

To avoid such ambiguity, C++ provides an alternate syntax when dealing with accessing member objects for objects being pointed to by variables. This is the arrow syntax (typed as a dash followed by the greater than symbol). For example,

    aNum = fractPtr->getNumerator();
  

has the same effect as the statement above that uses the asterisk syntax.

When accessing member objects for objects that have a pointer to them, it is best to use the arrow syntax.

The more common reason pointers are used in a C++ program is to be able to allocate space dynamically. Often times, when one writes a program, it is not clear how many data items are to be processed, and there may not be any good upper bound on the size of the data. For example, when writing a program to sort grades for students, the number of students is not always known when writing the program. In fact, any chosen upper bound may prove inadequate, and then one may have to change the bound and then recompile. Instead, it would be better to write a program that uses as much space as necessary for a given run. This is called dynamic memory allocation, i.e., getting memory allocated at run-time, rather than at compile time. Such dynamically allocated memory is given to the program by the system from heap memory rather than stack memory.

The operator new of C++ is the mechanism to get memory allocated at run-time. For example,

    int * numPtr;
    numPtr = new int;
  

will allocate one word for the pointer variable numPtr on the stack at compile time. Then, when the program is executed, the second statement will allocate one word of memory on the heap, and the new operator will return the address of this word of memory. This address is stored in numPtr.

Arrays can be allocated space similarly at run-time. For example, suppose the IS contains the number of student grades followed by the grades themselves, then the following function will read in the number of grades first, then get space allocated for the grades, and then read in the grades.

    void readGrades () {
      int numGrades;
      int * grades;

      cin >> numGrades;
      grades = new int[numGrades]; // allocate space for the grades.
      for (int index = 0; index < numGrades; index++) {
        cin >> grades[index];
      }
    }
  

Note that when the above function ends, space allocated for the variable numGrades and the pointer variable grades will be deallocated (given back to the system), but the space allocated dynamically for the array will remain allocated to the program. Nonetheless, the address for these words of memory is lost (when grades was deallocated) and so we cannot access that memory in the program anymore. Moreover, that memory will not be allocated by the system to this program, or to any other program on the computer until this program finishes. The above is an example of a memory leak, i.e., memory that is allocated to a program but un-accessible to the program.

To correctly use dynamic memory without causing memory leaks, once a program is done using any dynamic memory it got allocated, the program must explicitly deallocate that memory using the delete operator. For example,

    delete numPtr;
    delete [] grades;
  

Note the use of the square brackets when deleting memory that is an array. Note also that the operator delete is given the pointer to the memory locations that need to be deallocated.