ClassS04CS141 | recent changes | Preferences

We highly recommend learning the Emacs editor, and how to compile and debug programs from within Emacs. -- A basic intro to creating C++ programs with Emacs on linux. -- A reference card with emacs commands. -- how to compile and debug from within emacs:

Interpreting error messages generated by the compiler

Here is an example of an error message output by the g++ compiler on Linux.

 ../ In member function `void Array<VALUE>::clear() [with VALUE = SelfInitInt?]':                                                                           instantiated from here                                                       
 ../ no match for `SelfInitInt?& = int' operator                                      
 test_utilities.h:5: candidates are: SelfInitInt?& SelfInitInt?::operator=(const SelfInitInt?&)                                                                                 

This example is relatively small, but it will serve our purpose.

The relevant lines of code are

 62:  Array<SelfInitInt?> D;
 63:  SelfInitInt? x;
 64:  test(D[1].i == x.i, "Array declared without default value should not be initialized");
 65:  D[1].i = 10;
 66:  D.clear();


 110: template<class VALUE>
 111: void Array<VALUE>::clear()
 112: {
 113:   max_referenced = -1; 
 114:   table_size = 20; 
 115:   use_default = 0; 

The first two error messages from the compiler are these:

 ../ In member function `void Array<VALUE>::clear() [with VALUE = SelfInitInt?]':                                                                           instantiated from here                                                       

The first line tells us that there is a problem with the member function clear() of the Array<SelfInitInt?> class. The second line tells us that the reason the compiler is trying to compile the clear() function for Array<SelfInit?> is because of the code on line 66 of

The next two lines are

 ../ no match for `SelfInitInt?& = int' operator                                      
 test_utilities.h:5: candidates are: SelfInitInt?& SelfInitInt?::operator=(const SelfInitInt?&)                                                                                 

These say, respectively, that the compiler is trying and failing to interpret the line " use_default = 0; " on line 115 of, and that the closest match the compiler could find (to try to assign an integer type to use_default, which is of type SelfInitInt?) was on line 5 of test_utilities.h, where the SelfInitInt? class is defined.

The error is on line 115 of Namely, one cannot assign 0 to a variable of type SelfInitInt.?

Often one has to have some knowledge of what the compiler is trying to do in order to interpret error messages. In C++, the types of all variables, functions, and expressions are known at compile time. When the compiler sees a function call, or a member function call, it looks at the name of the function and the types of the parameters. If it has seen a function declaration with the same name and parameter types, it compiles in a call to that function. Otherwise, it tries a combination of type conversion (e.g. converting an int to a double) and instantiation from a template (creating a specific function from a templated function definition) in order to figure out what specific function to call. If this process fails to find a good match, the kind of error message we see above results.

Link-time errors

Linking is the second phase of the compiler. In this phase, the compiler takes all of the compiled files and functions and combines them together, resolving function calls in one file to call the appropriate compiled functions, possibly from other files. The most common error at this stage is that, for some function or functions that were called somewhere, the appropriate compiled function cannot be found. If you see many errors of this type, you may not be compiling all the necessary files, or you may have failed to "#include" some files in your program. Another error is that a single function is found to have been compiled more than once, in which case either you have defined that function twice in your files, or you have somehow included a file twice in a way that makes the compiler compile the code for it multiple times.

Debugging run-time errors

Once your program compiles, it will probably still not do quite the right thing. It may give the wrong output, or it may die with a SEG FAULT, BUS ERROR, or other signal that indicates that your program is misbehaving and has been terminated by the operating system. Errors of the latter kind can be caused by, for example, dividing by 0, although by far the most common type of run-time error causing a C++ program to terminate with a signal is memory corruption of some kind. This can be caused by inappropriate use of pointers. For example, if a pointer, say P, has value NULL (that is, 0) or some other inappropriate value, then dereferencing the pointer (as in "*P" or "P->..." or "P[1]") may cause a seg fault or bus error.

On the other hand, such errors may not cause an immediate termination of the program. For example, if you access an array index that is out of bounds, as in

  int A[5];   A[5] = 0;

the compiler will not complain, but when the assignment "A[5]=0" is done, the 0 will be overwriting some memory that is not actually part of the array. This could mangle other data, or it could even mangle the code of the program in memory itself, causing very strange behavior later on in your program.

Using the debugger to isolate run-time errors

When you have a run-time error, make sure your program is compiled with the "-g" option, then run the program using the debugger. (See the top of this note for more information on how to do that.) The general strategy when using the debugger to track down an error is this:

  1. Find the point in the execution of the program when you first see the problem. For example, find the line of code just before the SEG FAULT occurs. If the problem is a signal such as SEG FAULT or BUS ERROR, simply run the program in the debugger. The debugger will catch the signal and stop the program when the signal occurs. By examining the stack (using "where", "up", and "down") you can see at what point in the execution the problem becomes visible. For other run-time problems, you may have to step through the program (set a breakpoint in "main" using "break main", then "run" the program, then use "next" and "step" to step through the program until the error occurs.
  2. Once you know where the problem first becomes visible, set a breakpoint just before that point and re-run the program, stopping it just before the error occurs. Next, use "print" to examine the current values of variables. Look for a variable that has a strange or unlikely value, including pointers whose values are 0 or small. You are looking for a variable that has a wrong value, one that would cause the symptom you've observed.
  3. Next, re-run the program, using breakpoints and printing that messed up variable to find out when the variable first becomes messed up. You are looking for the point in the program where the first wrong thing happens. In this way, you can trace the problem back to the line or lines of code that are incorrect.

Another useful debugging strategy is to print your program out on paper and examine it that way, scanning the entire program for mistakes. Sometimes seeing the entire program on paper makes it easier to find mistakes.

Debugging run-time memory errors

A sophisticated tool for debugging run-time memory errors (mistakes with pointers, array indices out of bounds, etc) is "valgrind". You can download a binary [here], and you can find documentation [here].

ClassS04CS141 | recent changes | Preferences
This page is read-only | View other revisions
Last edited April 24, 2004 1:09 pm by Neal (diff)