CSCE 2004 - Laboratory Assignment 8

The objective of this lab is to know more about pointers in C++ and dynamic allocation of memory. This lab assignment has the following steps:

  1. Pointer Declaration
  2. Your program's variables and instructions when loaded into memory occupy a certain range of memory. We can find out the address of a variable using the '&' operator. For example,

    	int var1=20;
    	cout << "The address of var1 is " << &var1 << endl;
    
    

    To increase your programming power, you need an additional idea: Variables that hold an address value called a pointer variable or a pointer, defined using the '*' operator. For example,

    	int *var1;   //defines a pointer variable that holds an address pointing to an integer.
    	char *name;  //defines a pointer variable that holds an address pointing to a character.
    

    The value that the variable holds can be accessed using the '*' operator before the variable name. This operation is sometimes called "using the pointer". To summarize the above concepts, run the code given below and check its output. Paste the output below.

    #include <iostream>
    using namespace std;
    
    int main()
    {
    	float f; 
    	float *f1; //declares a pointer variable 'f1' pointing to a float
    	f1=&f; //must set the first pointer to point somewhere before you use it
    	*f1 = 0.09; //assigns a value to the variable
    
    	cout << "The address of f1 is " << f1 << endl; //prints the address of f1.
    	cout << "The value that f1 points to is " << *f1 << endl; //prints the value that f1 points to.
    	cout << "The value of f is " << f << endl; //prints the value of f.
    
    	char ch; //declare a 'char' variable
    	char *ch1 = &ch; //declare a pointer to the variable
    	*ch1='Y'; //assign ch a value
    
    	cout << "The address of ch1 is " << ch1 << endl; //prints the address of ch.
    	cout << "The value that ch1 points to " << *ch1 << endl; //prints the value that ch points to.
    	cout << "The value of ch is " << ch << endl; //prints the value that ch points to.
    
    	return 0;
    }
    
    
    

    Compile and run the above program. Make sure you understand each of its outputs before continuing!

  3. Dynamic Allocation of Arrays
  4. Declaring an array with a fixed size like

    	int array[1000];
    has two typical problems:

    * Exceeding maximum. Choosing a real maximum is often impossible because the programmer has no control over the size of the data sets the user is interested in. Erroneous assumptions that a maximum will never be exceeded are the source of many programming bugs. Declaring very large arrays can be extremely wasteful of memory, and if there are many such arrays, may prevent the program from running in some systems.

    * No expansion. Using a small size may be more efficient for the typical data set, but prevents the program from running with larger data sets. If array limits are not checked, large data sets will run over the end of an array with disastrous consequences. Fixed size arrays can not expand as needed.

    These problems can be avoided by dynamically allocating an array of the right size, or dynamically expanding a small array to make it larger as needed. Both of these are done by declaring an array as a pointer and using the new operator to allocate memory, and delete to free memory that is no longer needed.

    Specifically, to expand an array, you must:

    1. Create a new array that is twice is the size of the first array
    2. Copy the data from the old array to the new array
    3. Delete the old array

    To create a variable that will point to a dynamically allocated array, declare it as a pointer to the element type. For example,

    	int *array = NULL;
    creates a pointer to an array of integers. We usually initialize this pointer to NULL just to be safe. The above declaration creates a pointer, but does NOT allocate any memory for the array.

    Allocating memory: Allocating an array with 'new'.

    When the desired size of an array is known, allocate memory for it with the 'new' operator and save the address of that memory in the pointer. Pointers may be subscripted just as arrays are. Here is the formal definition of new:

    	void* operator new[] (std::size_t size) //not actual code

    The example below reads in a number and allocates that size array.

    	int *array = NULL; //declare array pointer
    
    	int num; // size needed for array
    	cout << "Enter a number for array size: ";
    	cin >> num;
    
    	array = new int[num]; //allocate num ints and save their address in 'array'
    
    	if (array!=NULL)
    	{
    		// initialize all elements to value of counter
            	for (int i=0; i < num; i++)
                    	array[i]=i;
    	}
    
    	delete [] array; // return space to the system
    	*array = NULL; // important to set the pointer to NULL after the values it points to are deleted
    
    

    Compile this program and run it. Make sure you understand each of its outputs before continuing!

    Deallocating memory: Freeing unusued memory with 'delete'.

    When you are finished with dynamically allocated memory, deallocate it with the 'delete' operator. To release the space for an array, use 'delete [] ptr'. To release space in general, use 'delete ptr'.

    Whenever you use 'delete' to deallocate memory, make sure to set the pointer to that memory back to NULL as well.

    After memory has been deallocated, it can be reused by later 'new' requests. Memory that your program did not deallocate will be automatically deallocated when the program terminates. Never deallocate memory that was not dynamically allocated - the results are unpredictable.

  5. How Things Can Go Wrong
    1. First Problem: Delete/change an alias, use the original...
    2. When two pointers both point to the same location, changes to the value or using the 'delete' operator on one of the pointers affects the other. See the following example:

         float *fptr1; // a float pointer
         float *fptr2; // another float pointer
      
         fptr1 = new float; // use 'new' to make a float for the first pointer to point to
         *fptr1 = 7.3; // and give that float some value
         fptr2 = fptr1; // now, make the second pointer point to the same spot
        
         // Print out the values of the floats that the pointers point to
         cout << "The value that fptr1 points to is: " << *fptr1 << endl;
         cout << "The value that fptr2 points to is: " << *fptr2 << endl;
      
         *fptr2 = 3.3; // and now.. change the value of the float..
      
         // Print them out again -- can be confusing... who changed *fptr1?
         cout << "The value that fptr1 points to is: " << *fptr1 << endl;
         cout << "The value that fptr2 points to is: " << *fptr2 << endl;   
      
         // get rid of fptr2
         delete fptr2; // deallocates the memory where fptr2 points
         fptr2 = NULL; // makes fptr2 point nowhere
      
         // we still have fptr1, right? Let's see what's there.
         cout << "The value that fptr1 points to is: " << *fptr1 << endl;
      
         // oops
      
      

      In order to fix any potential problems with printing out values from pointers, add something of the following form (tailored as needed for your program) around any print statements:

         if (ptr != NULL)
      Update the above code segment to avoid printing out from a memory location that has been deallocated, and run it to ensure it's functioning correctly.

    3. Second Problem: Forgetting to delete... out of memory!
    4. It is very important to remember to use 'delete' to deallocate any memory that was allocated with 'new' once it is done being used. Consider the following segment of code:

      #include <iostream>
      using namespace std;
      
      void foo()
      {
         int *localarray = NULL;
         localarray = new int[5000];
      }
      
      int main()
      {
         for(int i = 0; i < 1000000; i++)
            foo();
             
         return 0;
      }
      

      As you can see in the above code, main() consists of a single loop that makes one million calls to the function foo(). Each time foo() is called, it dynamically allocates memory for an array of 5000 integers. Try running this code. (It should crash in a brand new way!)

      The reason this program terminates is because it allocates all available memory through successive calls to foo(), and then tries to allocate more. To prevent this type of crash, you must use the 'delete' operator in foo() to deallocate each instance of the array after foo() is done using it (or in this case, not using it).

      Make the correct addition to the above code so that it doesn't crash and run the program again to ensure it's functioning.

  6. Expanding an Array
  7. Based on the above concepts write a new, small program that allocates space for an array of integers with SIZE=5 and goes in a loop to initialize the array with the values equal to iteration step (i.e., from 0 to 4). Then, try to use the memory allocation commands introduced above to create a new array that is double the size of the original array. Next, copy the values from the original array to the new array. Then, fill in the remaining elements of the new array with the value corresponding to the iteration step of the previous loop (i.e., from 5 to 9).

  8. Dynamic Memory and Functions
  9. OPTIONAL: The following step is 100% optional, but you should read over it and think about how you would do it even if you don't get to it during lab.

    Using the previous step as a model, begin a new program. Write a function that takes as input (1) a dynamically allocated array of integers and (2) an integer representing the size of the array. The function should create an array of doubled size, copy the first array into the first half of the new array, and initialize the second half of the new array to all -1. It should also return the size of the new array by reference.

    Call your function twice from main() with dynamically allocated arrays of integers of two different sizes filled with (arbitrary) integer values of your choice. Before each function call, print the arrays to the screen. At the end of each call, print the arrays to the screen again. Make sure that they are twice as large and the second halves are all -1s. Make sure that the loops used to print your arrays are controlled by a single 'size' variable that is updated by your function. (i.e. Do not hardcode the number of loops in your prints!)

    Hint: To get a dynamically allocated array into a function, pass a pointer to it.

  10. Submit Your Work
  11. To submit your lab work, have your TA check over any code/output that you've done during this lab session and record that you completed the lab assignment.

    Important Note: It is your responsibility, not the TA's, to ensure that your lab is recorded as being completed before you leave! Although the labs are intended to be completed in the alloted lab time, if you need additional time to finish the lab assignment, you have 24 hours from the end of the lab meeting time to finish. If so, you should ask your TA for instructions on how to submit the lab assignment after the lab ends.

  12. Logout of the System
  13. After making sure that you submitted the code correctly and you have done the entire assignment, logout of the system by clicking on the menu button in the lower left hand side of the desktop. Then, click on Log Off. Wait a few seconds for a window to pop up and then click Log Off.