CS 112 Lab 2: Pointers and the Debugger


In this exercise, you will:
  1. Declare, use, and dereference pointer variables.
  2. Use pointers to process the items in arrays.
  3. Use the debugger to execute a program statement by statement.


Last week, we saw how data structures can be useful in solving "real world" problems. We also saw how test-driven development can help us build robust data structures.

Today's exercise has two parts:

  1. We will begin by doing some software "experiments" to learn about pointers, addresses, and memory; and
  2. We will end by starting to build a simple vector data structure.
Create a new project named lab02 in which to store this week's files. Then download and import part1.cpp into your project. When you save it, you may get some warnings that the variables are unused. You can ignore those for now.

Read through the entire Part I exercise before you begin working on it, as that will save you some aggravation.

Part I

The first part of this week's exercise is to practice thinking about variables, addresses. and values. If you have not already done so, open part1.cpp and look it over. The first part of this exercise is to draw an accurate diagram of the memory allocated to these variables, complete with memory addresses.

To accomplish this, you can use one or more output statements to display the address of each variable. To display the address of a variable, you can use the address-of operator (e.g., &variableName) that returns the address of the variable to which it is applied.

Add statements to part1.cpp that will display the name of each variable and its address, so that you can see at a glance the address associated with each variable.

Using the information this yields, use a spreadsheet (or lined paper if no spreadsheet is available) to draw an accurate diagram of the memory in which all of these variables are stored, including the pointer variables. Your diagram should show your memory as one or more vertical columns, using each line of the diagram to represent 1 byte. Show the hexadecimal address of each byte, and clearly indicate which bytes are associated with which variable.

For example, if there were three just variables x, y, and z, with sizes 2, 1, and 4 bytes, respectively, and you found them to be associated with addresses 0x1000, 0x1002, and 0x1003 respectively, then your diagram might look something like this:

Questions. On your diagram, answer the following questions:

  1. How much memory is allocated to the following types? bool, short, int, long, float, double, long double
  2. How much memory is allocated to each of these types? bool*, short*, int*, long*, float*, double*, long double*
If you encounter a problem determining how much memory is allocated for a given type, the sizeof(type) operation can be used to determine the number of bytes associated with a value of type type. This operation can also be used to deduce a variable's address, if the addresses of surrounding variables are known.

One other issue is that the GNU C++ compiler does not seem to like to display the addresses of char variables. A workaround is to trick the compiler by converting the char variable address's type from char* to a type that the compiler will display. One way to do this is to cast the address from char* to void* as follows:

   cout << "charVal1: " << static_cast<void*>(&charVal1) << endl;

To avoid the tedium of typing all those hex addresses into your spreadsheet, you may find this program hexList.cpp to be useful. To use it, you will need to first record the hexadecimal numbers that part1.cpp produces (e.g., save them in a text file), and then identify the maximum and minimum of those numbers. These are the 'highest' and 'lowest' memory addresses where your variables reside. (These are not necessarily the first and last values.)

Once you have made a note of those addresses, you are ready to use the hexList.cpp program. Download it, create a new project, import, compile, and run it, and it will let you generate ascending or descending sequences of hex numbers, which you can copy/paste into your spreadsheet.

(This program uses getline() to read in each number you enter as a string, and then converts the digits in that string into a number, so be careful that you do not enter any spaces when you enter the number.)

When your spreadsheet is done, save it in an Excel-compatible format into your lab02 folder for this exercise.

Part II

In this second part of today's exercise, we will explore how pointers can be used to process dynamically-allocated arrays. We will also start using the Eclipse debugger. A debugger can be invaluable in debugging your programs in this course, so I highly recommend you practice using it anytime you need to track down a logic error in your program.

Getting Started. Begin by "commenting out" the entire main() function in part1.cpp. Then import part2.cpp, array.h, and array.cpp into your project. Open each of these files, verify that they compile without error; then continue.

Array processing. Run the program in part2.cpp, and compare its output with the statements it contains. In particular, note the following:

  1. The main() function contains the statement:
    double *a1 = new double[SIZE];
    In this statement:
    1. The new operation requests enough memory from the system to store an array of SIZE values of type double.
    2. If the system is able to fulfill this request, the new operation returns the address of the first byte of that block of memory, otherwise the new operator returns 0 (NULL); and
    3. The assignment operator (=) stores whatever value new returns in variable a1.
    Because this memory allocation occurs as the program is running (as opposed to when the program is compiled), such an array is called a dynamically allocated array (or just dynamic array).
  2. The initialize() function has a parameter named a of type double*. However, the body of that function uses the subscript operator ([i]) on a, just as though it were a double array. The subscript operation can thus be used on pointer variables that store the base address of an array.
  3. The print() function has a parameter named a of type double*. However this function accesses each value of the array in turn by:
    1. Using the dereferencing operator (*) to access the double to which a is currently pointing; and
    2. Using the pointer increment operation (++) to advance the pointer to the address that follows that double.
    Pointers thus provide an alternative mechanism for accessing the values in an array.
  4. The average() function returns the wrong value. Fixing this is the final piece of today's exercise.
  5. At the end of the main() function, the statement:
       delete [] a1;
    returns the storage to which a1 points back to the system. To return a dynamically-allocated array to the system, the delete must include the subscript ([]) symbol, or else only the first element of the array will be returned to the system.
  6. The main() function contains the declaration:
    double a2[SIZE];
    The memory for this array is fixed when the program is compiled, and it cannot be changed as your program runs. Such arrays are called statically allocated arrays (or just static arrays). Dynamically allocated arrays are much more flexible than statically allocated arrays, but require the programmer to explicitly manage memory (i.e., allocate and deallocate it).

Using the Debugger. Before we fix average(), we want to introduce you to a tool specially designed to help you find logic errors in your program -- errors where your program compiles correctly, but does not run correctly. This tool is appropriately called the debugger, and Eclipse's C++ plugin uses the GNU debugger, which is named gdb.

To use gdb from within Eclipse, go to the Run menu and choose Debug... (Alternatively, you can just click the Debug button -- -- which should do the same thing.) Depending on how Eclipse is configured, you may or may not see a Select Preferred Launcher dialog, containing a list of ways to launch the debugger. If this dialog appears, select Standard Create Process Launcher from the list.

Eclipse should then should then display a Debug dialog that looks a lot like the Run dialog. If the Debug button at the bottom is not dimmed, click it. (If it is dimmed, click the Debugger tab and check that a debugger is selected in the Debugger combo box that appears.)

The debugger uses a different perspective than the normal C/C++ perspective, so Eclipse will ask you to confirm that you want to change the perspective. (Click Yes.) Eclipse will then

  1. rebuild your program with the information needed by the debugger;
  2. display its debugging perspective;
  3. start running your program; and
  4. suspend your program at the first statement in its main() function.
Near the top-left corner of this perspective is a Debug tab. If you read through the text there, you will see that it says your program is currently suspended.

Below the Debug tab is a code window displaying your program. Note that the first statement in the main() function is highlighted, and there is a blue arrow beside that statement:

Whenever your program is suspended, Eclipse displays this blue arrow to indicate the statement that will be performed next when your program resumes execution (see below).

To the right of the Debug area you should see a Variables tab, above a window in which you should see listed the variables in your main() function. Note that the value of each variable is displayed.

Being able to view all of a function's variables and their values as your program is executed is one of the most useful aspects of a debugger.

Between the Debug tab and the Variables tab is a debugging toolbar:

For our immediate purposes, the most important buttons on this toolbar are:

Take a moment to look over the value of SIZE in your Variables tab; then press the Step over button. You should see the blue arrow advance to the next statement. Does SIZE change its value?

Check the value of a1 in the Variables tab; then press the Step over button again. You should again see the blue arrow advance to the next statement. Does a1 change value?

At this point, your blue arrow should be at the assert() statement. Use the Step over button to execute that statement in its entirety.

Your blue arrow should now be at the statement that calls the initialize() function. If we were to press Step over again, this statement (and function) would be executed in its entirety. So instead of doing that, press Step into. Your code and Variables areas should change to indicate that you are now "inside" the initialize() function. Look over your Variables area, and then press Step over several times. Note how the values of your function's variables change as each statement is performed. Note also how easy it is to trace the repetition of the function's for loop!

Continue to use Step over until control returns to the output statement in the main() function. Use Step over to step over this output statement. Then use Step into to step into the print() function. Again, note the change of the Variables area, to reflect the variables within print(). If you click the triangle next to the name of parameter a, you can view not only the value stored in a (an address), but can also view the value at that address. With a "opened up" in this way, click Step over enough times to execute the a++; statement. What effect does the a++; statement have on the values shown in the Variables tab?

Click Step over several more times, watching the effect each statement has on the variables.

Anytime you are inside a function, the Step return button can be pressed. When you press this button, the remainder of the function is performed. When control returns to the function's caller, your program suspends again. Press it now, and control should return to the statement in main() containing the call to average().

The Step over and Step into buttons are the two most frequently used buttons in debugging. Others that are also useful include:

Use the debugger to step through the remainder of the code in part2.cpp. Note in particular that the same operations work equally well on either dynamically or statically allocated arrays. If you "open up" a2 and a1, does the debugger display the same information or different information about each array?

Debugger Exercise. Work through the following steps with a classmate, and discuss what you see happening at each step.

  1. Click the red Terminate button to halt the current execution.
  2. In array.cpp, locate the body of the initialize() method and set a breakpoint on the first line inside the method.
  3. Click the Debug button to start a new debugging execution.
  4. When execution stops in the main() method, click the green Resume button. Your execution should resume, continue, and then stop at the breakpoint you set inside the initialize() method.
  5. To the right of the Variables tab, there is a tab called Expr, in which you can enter arbitrary expressions. Click on this tab.
  6. To enter an expression, click the green Plus sign on the toolbar under the Expr tab. In the dialog that appears, enter a and click the Ok button. Then repeat this process to enter the expressions: *a, i, and a[i]. When you are done, you should have four expressions listed.
  7. Now use the Step Into button to step through the statements in initialize(). Each time you step through a statement, watch how it affects the expressions in your Expr area. Do the values of a or *a change? What about i and a[i]? How do the values you see compare to the values the for loop is storing in the array?
  8. When you reach the end of initialize(), continue stepping and you should see execution return to the main() function. What happens to the values of your expressions then? Why?
  9. There, Step Into the first call to print(). and then step through its statements. Each time you step through a statement, watch how it affects the expressions in your Expr area. Do the values of a or *a change? What about i and a[i]? How do changes to the value of a affect the expressions *a and a[i]?
  10. Use the debugging controls to step through the rest of your program, noting how they let you see the effects a statement has on variables and their values.
The Variables tab thus lets you see a variable's value; the Expr tab lets you see the value of an arbitrary expression at a given point in your program, which can be very useful when you need to track down a logic error.

To leave the debugger and return to the normal C/C++ perspective, click the Terminate button, then use either the C/C++ button or the Open Perspective button (both are located near the upper right corner of the screen) to change from the Debugger perspective back to the C/C++ perspective.

Congratulations! You've just used the Eclipse debugger!

Your Turn. Even without the debugger, you should be able to see that something is wrong with the average() function. For the rest of this exercise, you are to "fix" this function, without using the subscript ([]) operator! Use what you have learned in this lab to write this function to compute the average of the values in the array using a pointer variable and pointer operations instead of the subscript operator.

Hint: How does the print() function access each entry of the array it is passed?

Turn In

Copy your lab2 folder into your /home/cs/112/current/ folder, so that the grader can check your spreadsheet, your modified part1.cpp, and your average() function (defined in array.cpp and called in part2.cpp).

If time permits, you may begin working on this week's project.

CS > 112 > Labs > 02

This page maintained by Joel Adams.