UP ONE LEVEL: ENEL 315 Winter 1999 Handout List

ENEL 315 Winter 1999 Lab[2]
For the week of January 25

Author: Steve Norman
Paper copies handed out: 22 January 1999
Last modified: Tue Jan 26 14:17:44 MST 1999

Contents


Code availability

Files needed for this lab will be copied to the engg Linux system by 6:00pm, Friday, Jan. 22.

[back to top of document]


Corrections

[back to top of document]


This is an Individual Assignment

Each student must work independently and hand in his or her own assignment.

[back to top of document]


Due Dates

WARNING: Due date patterns may change from week to week. It is your responsibility to check the due dates on each lab handout.
LAB TIME      SECTIONS   DUE DATE        LATE DUE DATE

Tue  2:00pm   B01/B02    Mon Feb  1      Tue Feb  2
Thu  2:00pm   B03/B04    Mon Feb  1      Tue Feb  2
Assignments are collected on the Due Date and Late Due Date at 1:00pm. The penalty for handing in an assignment after the Due Date but before the Late Due Date is 3 marks. For example, 12/13 becomes 9/13 if the assignment is late. There will be no credit for assignments turned in after the Late Due Date; they will be returned unmarked.

[back to top of document]


What to Hand In / Marking Scheme

A: hand-written answers to questions (2 marks)
B: nothing
C: nothing
D: hand-written table of data obtained with gdb (2 marks)
E: hand-written table of data obtained with gdb (2 marks)
F: hand-written answers to questions (2 marks)
G: five printouts (5 marks)

total: 13 marks

Follow the instructions on the Lab[1] regarding a cover page, how to staple your assignment, and so on. Please remember to put your lab section number on the cover page.

[back to top of document]


Exercise A: Introduction to make

Read This First

Before proceeding, read the handout Multi-File Projects and Makefiles.

What to do

As you work through this exercise, write down answers to all of the questions that are asked.

Step 1. Create a copy of the directory /home/engg/enel315/lab2/exA
Look over the code in the files so you can see what kind of executable will be built. (CORRECTION: The line #include "OrdList.H" should be changed to #include "OrdList.h" in useOrdList.cpp.)

Use the commands

  g++w -c OrdList.cpp
  g++w -c useOrdList.cpp
  g++w -o useOrdList OrdList.o useOrdList.o
to build the executable useOrdList. Run the executable just to make sure that you have built it correctly.

Step 2. Examine the files OrdList.cpp, OrdList.h and useOrdList.cpp. Notice that OrdList.cpp and useOrdList.cpp both include the header file OrdList.h. This means that OrdList.cpp and useOrdList.cpp depend on OrdList.h--the translation units for the two .cpp files both contain code from OrdList.h. If the contents of OrdList.h are changed then both OrdList.cpp and useOrdList.cpp must be compiled again.

Step 3. Create a file named Makefile using Emacs.

Important Emacs note: In order to get proper entry of tab characters when editing a makefile, you must make sure that Emacs is in Makefile mode. To check what mode Emacs is in, look at the Emacs modeline, which is the line that looks something like this:

--**-Emacs: Makefile          (Makefile)--L1--All----------
The contents of the parentheses tell you what mode Emacs is using to edit the buffer. So, for example, if you see this modeline:
--**-Emacs: Makefile          (Fundamental)--L1--All-------
then Emacs is in Fundamental mode, not Makefile mode. Normally, Emacs will automatically go into Makefile mode whenever you edit a file called makefile or Makefile. However, there are ways to start editing a makefile that will not properly start Makefile mode. To force Emacs into Makefile mode, use the following Emacs command:
M-x makefile-mode
(In other words, press the Esc key, then the x key, then type makefile-mode and press the Enter key.) After getting into Makefile mode, check to see that you have tab characters, NOT groups of space characters, wherever tab characters are needed.

The contents of Makefile should be

# Makefile for useOrdList
# Author:      (YOUR NAME)
# ID Number:   (YOUR ID NUMBER)
# Lab Section: (YOUR LAB SECTION)

CXX = g++w
THE_PROG = useOrdList
OBJS = useOrdList.o OrdList.o

$(THE_PROG): $(OBJS)
	$(CXX) -o $(THE_PROG) $(OBJS)

useOrdList.o: useOrdList.cpp OrdList.h
	$(CXX) -c useOrdList.cpp

OrdList.o: OrdList.cpp OrdList.h
	$(CXX) -c OrdList.cpp

The space preceding $(CXX) at the beginning of the lines MUST be a tab character obtained by using the Tab key on the keyboard.

Execute the command

  rm OrdList.o useOrdList.o useOrdList
to delete the object and executable files created previously.

Execute the make command. Observe that first the required .o files are created, and then they are linked together with the required library files to create the executable file useOrdList.

Execute make again. This time, no compilation or linking should occur, because the executable file useOrdList is newer than the .o files on which it depends and the .o files are newer than the .cpp and .h files on which they depend.

Step 4. Modify useOrdList.cpp by changing the argument in the first function call from 200 to 198.7654, and execute make again.

Why was useOrdList.cpp recompiled? Why was OrdList.cpp not recompiled? Why were useOrdList.o and OrdList.o linked again?

Run the new executable. Notice the effect of converting the double constant 198.7654 to an int.

Step 5. Modify OrdList.h by changing the ItemType from int to double. Execute make again, and run the new executable. What happened? Why were both .cpp files compiled this time?

[back to top of document]


Exercise B: Compiling from within Emacs

Read This First

Emacs has many features designed to help programmers. One such feature is a command that lets you run make from within Emacs. As you will see, it's very convenient.

What To Do

First, note these reminders:

Create a copy of the directory /home/engg/enel315/lab2/exB

Start up Emacs, and open the file Makefile. (Loading Makefile into Emacs is not an absolutely necessary step in this process, but it's a good way to make sure that you're in the right directory and that you actually have a Makefile to work with.) Within Emacs, type M-x compile, in other words, press the Esc key, then the x key, then type compile and press the Enter key (which is labeled Return on the DECstations). You will see

   Compile command: make -k
at the bottom of the screen. Press Enter to start the compilation process.

Three pieces of information and advice are in order here:

When the compilation process starts, you should observe that a new buffer called *compilation* is created, and that the output from make is written into this buffer. There are syntax errors in some of the source files for this program; you should be able to read the error messages from the compiler in the *compilation* buffer.

To find the lines in the source file where the errors occurred, you can use the Emacs C-x ` command. By ` I mean the ``backquote'' character, which is not not the same as ' , the ``single quote'' or apostrophe character. The backquote key is at the upper-left corner of the keyboard. (Unfortunately ` and ' look just about identical on the paper version of this handout! If you're confused, read this page with Netscape.) Repeated use of C-x ` cycles you through the entire list of error messages. Try this several times until you are comfortable with it. Notice how convenient it is that Emacs can find a file and move the cursor to the line that generated the compiler error.

Fix the errors and do M-x compile again to create an executable. Run the program from an xterm window.

Practice using M-x compile when you do the other exercises in this assignment.

Warning: Neither Emacs nor the compiler are ``smart'' enough to diagnose the actual cause of an error. Sometimes the real problem is located a few lines above the line where the error was discovered by the compiler; in nasty cases the real problem is hundreds of lines away, or in another file.

[back to top of document]


Exercise C: Introduction to the gdb debugger

Read This First

In my opinion, a debugger program is the third-most useful software tool available to a programmer. (Number one and number two would be the compiler and the text editor.)

A debugger is not, unfortunately, a program that reads programs and magically fixes all the bugs. Instead, it is a tool that lets the programmer inspect the execution of a program in minute detail, helping the programmer to collect information that can be used to diagnose defects.

Linux (and many other Unix systems) have a very good but somewhat hard-to-learn debugger called gdb. You will probably find gdb frustrating at first, but I urge you to keep using it until you start to like it. Once you have a basic level of skill you will find that examining a program with gdb is usually much faster (and always a lot more fun) than scattering cout statements in your code in the hope that the output will give you a clue about what is going wrong.

To make full use of gdb on an executable, the source code of the program must be available; this won't be a problem in ENEL 315 labs. As well, the program must have been compiled with a special command-line option (usually -g); this option causes the compiler and linker to put extra information such as line numbers, source file names, names of functions and variables into the object files and the executable file. This special option is automatically turned on for you when you use the gccw and g++w commands, so you don't need to worry about it when you use the the ENEL Linux systems.

In this exercise you will use gdb to find out why a segmentation fault occurs in when a certain C++ program is run.

What To Do

Copy /home/engg/enel315/lab2/exC
Read the file crasher.cpp to see what it does. The program manipulates a two-dimensional array; the output is supposed to be:
   my_array:
      0.0   0.2   0.4   0.6
      2.0   2.2   2.4   2.6
      4.0   4.2   4.4   4.6
If you spot the bug, leave it unfixed for now, since the point of the exercise is to examine a defective program with gdb. Load the Makefile into Emacs and run M-x compile to create an executable. Run the program, observing that it crashes with a segmentation fault.

Now run the program under the control of gdb. To start, enter the Unix command

   gdb crasher
When gdb gives you a prompt, type the command
   run
You should see some information on the screen, including
Program received signal SIGSEGV, Segmentation fault
which tells you that a segmentation fault occurred, and some information that looks like this (addresses of code and data may differ):
0x8048947 in mul_by_2 (z=0xbffff830, nr=3, nc=4) at crasher.cpp:60
60            z[i][j] *= 2.0;
which tells you the program was at line 60 in the function mul_by_2 when the segmentation fault occurred.

Enter the command

   bt
which shows the what the stack of function calls looked like when the program stopped--bt stands for ``backtrace''

Warning: Most programs with command-line interfaces do nothing when the user enters an ``empty'' command. But gdb usually responds to an ``empty'' command by repeating the previous command; this behaviour turns out to be useful for experienced users. If you get ``lost'' in gdb do NOT press Enter a few times to see what will happen, because that will only get you ``more lost''. (On the other hand, if you do get lost, entering the quit command and starting over might be a good idea.)

The commands up and down move you from one stack frame to another. (Stack frame is another term for activation record.) Unfortunately, the up command moves ``down'' the stack, and vice versa, according to the ENGG 335 and ENEL 315 convention for stack diagrams. (You'll have to get used to this sort of confusion; many programmers, profs and textbook authors think stacks grow up, but many others think stacks grow down.) Try the up and down commands several times each. Once you understand them, use down to get back to the stack frame for mul_by_2.

Type the command list to see the source code surrounding the line where the crash occurred. Can you see what is wrong with the code?

Finally, use the commands print i and print j to find out the values of i and j. How did i get such a strange value? If you still haven't spotted the bug, read the for statements very carefully.

To exit from gdb, use the quit command.

[back to top of document]


Exercise D: Examining a running program with gdb

Read This First

In Exercise C, you used gdb to inspect the ``wreckage'' of a crashed program. A debugger is also a useful tool for examining the behaviour of a running program. In this exercise you will use gdb to trace through the execution of a sort function in a C++ program.

What To Do

Copy the directory /home/engg/enel315/lab2/exD
Read the program use_sort.c and compile it and run it to see what it does.

Edit the program, replacing the initial contents of the array b in main with the digits of your ID number. For example, if your ID number is 907435, the declaration of b should be

    int b[ ] = { 9, 0, 7, 4, 3, 5 };
Your goal in this exercise is to record the contents of the array each time the program passes through point one.

Recompile your program. Start up gdb using the command gdb use_sort. Type in the run command. You should see the program run to completion, which doesn't help much in achieving the goal of the exercise. You need a way to get the program to pause in the middle of execution.

The crudest possible way to do this is to step through the program line by line. To do this, you must first set a breakpoint at the beginning of main, using the command

    b main
Then type run. Notice how execution stops with the line
  int b[ ] = { /* Your ID number digits appear here. */ };
displayed. It is important to realize that when program execution stops, gdb shows you the line of code that is about to be executed. So at the moment the breakpoint is encountered, the array b has not yet been initialized. The command to step one line forward is s. Enter the s command three times; this should get you to the line
     for(outer = 1; outer < n; outer++) {
At this point, you should ask gdb to display the contents of the array with the command
   disp a[0] @ n
which says, ``display n array elements starting with a[0]''. You should use disp and not print because disp sets up automatic display of the array every time the program stops. Now enter the s command repeatedly, watching for changes in the array contents. After you get the general idea, enter the quit command to get out of gdb.

Single-stepping is a rather tedious way to get the information you're looking for. A much more efficient method would be to have the program pause each time it passes through point one. You can do this by setting a breakpoint on the line

       tbi = a[outer];
Start up gdb again. To find the correct line number, enter the command
   list insertion_sort
The line you're looking for does not appear, so press Enter to continue the listing. You should see that 35 is the line number you want to use for the breakpoint. Set the breakpoint with the command
   b 35
Now type run to start the program, and notice that execution stops at the breakpoint. Enter the command
   disp a[0] @ n
to set up the display of the array contents. Then enter the command
   disp outer
so you can tell which pass of the outer loop is being executed. Write down the array contents and the value of outer on a piece of paper. Instead of single-stepping, enter the gdb command c (for ``continue''). This will continue execution until the breakpoint is encountered again. Write down the new array contents and the new value of outer. Keep using the c command and writing down information until the program has finished executing.

Make a table showing the value of outer and the array contents for each pass through point one.

[back to top of document]


Exercise E: gdb and Emacs

Read This First

In Exercises C and D you will have observed that gdb has a command-line interface independent of Emacs. However, you will probably find it more convenient to run gdb within Emacs, primarily because Emacs will do a nice job of showing you where you are in the execution of a program.

Here are a few things to take note of when using gdb within Emacs:

What To Do

First, to minimize confusion, shut down Emacs if it's currently running.

Make a directory called exE and in it, make copies of the files from Exercise D. Start up Emacs, and edit the array initializer in main so that it now contains the digits of your ID number in reverse order. So if your ID number is 907435, the declaration of b should be

    int b[ ] = { 5, 3, 4, 7, 0, 9 };

Recompile the program. Then, within Emacs, start up gdb with the command M-x gdb. You should see

   Run gdb: (like this) gdb
at the bottom of the Emacs screen. Add use_sort (the name of the executable) to the end of this line, and press Enter. A new buffer called *gud-use_sort* will appear; it is in this buffer that you will type gdb commands.

Enter b main to set a breakpoint at the beginning of main. Enter run to start the program. As a result, Emacs will make the use_sort.cpp buffer visible (if it wasn't already visible) and will place the => marker on the line of code that is about to be executed. Use the s command repeatedly to single-step through the program, carefully watching what happens in the source code buffer.

When you get tired of single-stepping, set a breakpoint at line 35, enter run to restart program execution, and repeat the procedure you used for Exercise D. When you have finished getting values of outer and array elements, be sure to give the quit command to gdb.

Make a table showing the value of outer and the array contents for each pass through point one.

[back to top of document]


Exercise F: A C++ array type, gdb and assertion failures

Read This First

In this exercise you will work with a class called Simple1dArray, which will give you some ideas about how a practical array type could be implemented in C++. (It's similar to some classes presented in ENGG 335.)

One of the member function prototypes may look weird at first glance:

const ElementType& at(int i) const;
Why does const appear twice? The two uses of const have quite distinct meanings. The const at the beginning of the line is part of the return type, which is const ElementType& - the function returns a reference to a const ElementType. The const at the end of the line says that this function is a const member function of the class Simple1dArray - calling this function cannot change the state of an object of type Simple1dArray.

What To Do

Copy the directory /home/engg/enel315/lab2/exF

CORRECTION. The following appeared in the original version of this handout:

Also copy insist.h and insist.cpp from /home/engg/enel315/insist into your exF directory.
There are no such files, and no such directory. You do not need them to do this exercise.

Start up Emacs, and use it to read through the program source files. Carefully read the comment titled REMARKS in the file SimpleArray.h. Build and run the executable; notice how there is an assertion failure when an out-of-range index is used.

Now start a gdb session within Emacs. Set a breakpoint at the beginning of main. Use the s and n commands to start single-stepping through the program.

It is important to know the difference between s (step) and n (next). The s command steps into function calls so that function execution can be traced line-by-line; the n command executes the current line without stopping in any functions that might be called. If you slip when tracing the execution of the program in this exercise, you will step into one of the ostream::operator << functions in the <iostream.h> part of the library; one of these gets called every time you use << on cout. This situation is annoying and can be a bit confusing.

To get out of a function, use the command fin (short for finish); this command will return you to the code that called the currently executing function.

To single-step without single-stepping into functions, use n instead of s.

Ask in the lab if you have questions about this.

You should observe that the constructor function is called twice. Keep single-stepping until the function bar is called. Step through bar, noting that sometimes the const version of at is called and sometimes the non-const version is called. Why does this happen? Write down your answer; you will hand it in as part of the assignment.

Now you will practice examining the state of a program at the point when an assertion failure occurs.

Restart the program by giving the run command. When the program stops at the beginning of main use the c command to have to program continue until it crashes. Use the bt command to see the sequence of function calls that led to the crash.

You will see that there are frames for several library functions on the stack. You won't see an assert function, because assert is a preprocessor macro, not a function.

What line in main caused the crash? What is the address of the object alpha? Write down your answers to hand in as part of the assignment. (Hint for the second question: The information from bt includes information about a this pointer.)

Use the up command repeatedly to work your way up to the stack frame (activation record) for main. Notice how the => marker jumps around from function to function and from file to file. (By the way, you can ignore the message ``No such file or directory'' as you work your way up through the library functions--the message is simply telling you that it can't find the source code for the library.)

In gdb, use the kill command to terminate execution of the program.

[back to top of document]


Exercise G: Big Three Review

What To Do

Create a new directory, and copy into it the files SimpleArray.h and SimpleArray.cpp from Exercise F.

Add a destructor, copy constructor, and assignment operator to the SimpleArray class. Create a file called checkSA.cpp file to test your new functions carefully. Marks will be based partly on the quality and clarity of your test code.

Write a Makefile for use in building the executable. Write it `from scratch'--do NOT simply copy and modify the Makefile from Exercise F. The point of doing this is to get used to the syntax and structure of simple makefiles.

When your program is complete, make five printouts: checkSA.cpp, SimpleArray.h, SimpleArray.cpp, Makefile, and program output.

[back to top of document]