Troubleshooting

PRINTING: Use landscape orientation and minimal margins to fit screenshots on page.

TAs: TAs are available to help you understand graphics programming, not fix bugs. Do not go to a TA until you are stuck after exhausting all the troubleshooting methods described below, and any other techniques you can think of.

Build vs. Runtime Problems: You will encounter two basic types of problems when developing your graphics programs: Build problems and Runtime problems. Build problems occur when your source code won't compile or link (see Building Executables). Runtime problems occur when you can successfully build and run your program, but it doesn't behave as expected or fails catastrophically (crashes or infinite loops).

Build Problems

Compile errors
Link errors

Runtime Problems (Debugging)

Starting/Stopping/Stepping
Breakpoints
Crashes
Call stack
Examining data

Build Problems

Errors in general


Seeing more text: Notice that there is not enough horizontal room in the Task List for wordy error messages and long file paths. To see more text, click on the error message in the Task List to highlight it as shown above, then hover the mouse cursor over the highlighted error message or file path to display the complete text in a temporary popup window.

Jumping to the problem line: Double clicking on an error message immediately opens the offending file for editing and places the cursor on the offending line in the file. A lot more of a highlighted long error message will display in the status line at the bottom of the window.

Getting help: When an error message is highlighted and has the focus, the first link in the Dynamic Help window at the bottom right of the screen is the error number of the message. Clicking that link displays more detailed help about the error message, which is often helpful. Pressing F1 does the same thing in this situation.

Compile errors

Undeclared identifiers: When the identifier is spelled and typed correctly, undeclared identifier errors are usually due to one of 2 possibilities:

  1. You didn't include a necessary header (.h) file, typically one supplied with a 3rd party library such as GLUT.
  2. The statement "using namespace std;" must be at the beginning of some .cpp files depending on which header (.h) files they include. The statement must be after all the #include statements.

Error C1083 "Cannot open include file: '': No such file or directory" usually occurs when you have a proper #include statement for a 3rd party library header (.h) file, but the compiler can't find it. To fix this error:

error C2381: 'exit' : redefinition; __declspec(noreturn) differs: See the GLUT resource page.

Link errors

Unresolved external symbol: The most common link error is an unresolved symbol like the example below for a function "void Unresolved()" declared in the HelloClass .h file, but with no function body defined in the HelloClass .cpp file. Note that in this example the symbol "Unresolved" must be used somewhere in the project to produce this error. The linker doesn't care if you declare things and don't define them as long as you never use them.

hello error LNK2019: unresolved external symbol "public: void __thiscall HelloClass::Unresolved(void)" (?Unresolved@HelloClass@@QAEXXZ) referenced in function "public: void __thiscall HelloClass::SayIt(void)" (?SayIt@HelloClass@@QAEXXZ)

The important information here is the actual offending symbol "HelloClass::Unresolved(void)". The message contains lots of other text, like ?Unresolved@HelloClass@@QAEXXZ, which is an internal representation of "HelloClass::Unresolved(void)", commonly referred to as a "mangled" or "decorated" name, a byproduct of C++ that you don't need to worry about for now.

In the above example, the unresolved symbol was in the project being built. More often it will be from a 3rd party library like GLUT. In that case, if the symbol is properly spelled and typed, you need to tell the linker to link with the 3rd party library by right clicking on the project name in the Solution Explorer tab, selecting Properties-> Linker -> Input -> Additional Dependencies, and entering the library file name, like glut32.lib.

fatal error LNK1104: cannot open file 'glut32.lib': Depending on where the 3rd party library file is located (may be different on different lab workstations or your home workstation), you may also need to tell the linker where to find it. This is the case if the 3rd party .lib file is not in the normal Visual Studio library directory, usually C:\Program Files\Microsoft Visual Studio .NET 2003\Vc7\lib. To do this, right click on the project name in the Solution Explorer tab, select Properties-> Linker -> General -> Additional Library Directories, and enter the directory path containing the 3rd party library file. 

Runtime Problems (Debugging)

  Tip: The book Practical Debugging in C++ by Ford and Teorey is short and relatively inexpensive, and has a chapter on Visual C++ version 6 that is easily adapted to Visual Studio .NET.

Starting/Stopping/Stepping

The debug toolbar

Starting: Pressing F5 or the first toolbar button will run the executable of the current StartUp project to completion or to the first breakpoint encountered. All changed source files are saved and rebuilt before running. To set the StartUp project, right click on the desired project name in the Solution Explorer tab and select Set as StartUp project. The project name now appears in bold.

Seeing console output: Since the homework assignments are console applications, they can and often do output text to a console (DOS) window through the cout and cerr streams. If you start with F5, the console window may display only momentarily so you will miss any useful messages. To keep the console window open after the program exits or aborts, start your executable by pressing Ctrl-F5 instead.

  Tip: Trial and error is generally faster than spending a lot of time in the debugger. When you observe a problem, take an educated guess of what it might be, make the appropriate, preferably small, source code change, and press F5 to rebuild, run, and quickly see the results of your changes. With typical rebuilds taking only a couple of seconds, you can try out several fix ideas per minute. Save stepping through code and examining data for tougher debugging problems.

Breaking an infinite (or very long) loop : Press the second toolbar button (looks like media pause button).

Stopping: Press the third toolbar button (looks like media stop button)., or press Shift-F5 to stop running the executable.

Resuming: Press F5 or the toolbar start button from any state in which the executable is stopped to resume its execution to completion or to the next breakpoint encountered.

Stepping: The last 3 toolbar buttons are Step Into (F11), Step Over (F10), and Step Out of, respectively, and refer to how they behave relative to the current or next program block demarcated by curly brackets{}. Step Into (F11) would be considered "normal" stepping.

  Tip: Stepping through lots of lines or loop iterations is slow. Set strategic breakpoints instead.

Getting to the source line where execution is stopped: Sometimes when your executable is stopped, like at a breakpoint, you will browse through many source files looking at stuff and thinking and effectively get lost so you don't remember where your program execution is currently at. Click the 5th toolbar button (right pointing yellow arrow) to return to the source file and line where execution is stopped.

Breakpoints

Setting/Clearing: The easiest way to toggle a breakpoint at a source line is to put the cursor on that line and press F9.A brown dot displays in the left margin of any line that has a breakpoint.

In loops: Set a breakpoint inside a loop and keep pressing F5 to execute the next iteration of the loop. This is quicker than stepping through every statement in the loop body.

Breaking when a variable is set to a certain value: Sometimes you want to find out for example, how a pointer gets set to null when the code looks OK. The Visual Studio allows conditional breakpoints based on the value of variables, but it's often easier to sprinkle debug statements in your code like

if (myPtr == null)
    int debug = 5;

and then set a breakpoint at the dummy statement "debug = 5;"

Crashes

If your program crashes while being debugged in the Visual Studio, it will stop at the crash point and display the exception(s) that caused the crash. The deepest throwing call is often in standard library or system code, but you can use the call stack to find which statement in your code caused the crash and the state of any variables at the time of crash.

Call stack

The deepest calls are at the top. Double click on a line to open that file and go to that line in the source editor pane. The yellow arrow indicates where current execution is stopped. Note that in this example, a standard library call has been stepped into. The green arrow indicates which call is currently displayed in the source editor. Note that it is a different call from where execution is stopped. You can freely navigate up and down the call stack to examine any variables that are in scope at each call level.

Examining data

Header file HelloClass.h for the example in the screenshot below:

#pragma once
class HelloClass
{
public:
    HelloClass(void);
    ~HelloClass(void);
    void SayIt();
    bool saidIt;
    float ramp[5];
};

When execution of your program is stopped, one or more panes at the lower left of the screen automatically display hierarchically the values of some variables. Note the yellow arrow indicating execution is stopped at the closing curly bracket } of the SayIt() function. This is your last opportunity to view the values of any variables in a block before they go out of scope. Also note that the Visual Studio knows the correct format to display variable values.

Autos: The Autos pane displays variables at file and class scope and variables used near the current line of execution, so variables come and go as you step through your code. Usually, this pane will show you just the information you need very compactly.

Locals: The Locals pane displays everything the Autos pane does, except local variables stay displayed for their entire block scope rather than come and go as you step through lines. This could become a very long list if the block being stepped through is large and complicated, but sometimes you need to see variables for their entire lifetimes.

Watch: If you need to examine a variable that is not showing in Autos or Locals, right click on the variable name in your source and select Add Watch. The variable is added to a Watch window, generally named Watch 1, where it continues to display until you delete it from the Watch pane with the Del key. Variables stay in the Watch pane even between debug sessions of the same program. Additionally, you can edit the expression in the Name column of the Watch pane used to evaluate the variable.

Changing variable values: You can simply edit and change the value of any variable displayed in a Watch, Autos, or Local pane. The change is immediate. This capability is of dubious value except in very specific "what if" circumstances.

Composite variables (arrays, classes, structs)

You can drill down into the hierarchy of composite variables by expanding and contracting branches with the + and - in the display pane.

Pointers

Pointers typically indicate whether they are valid or not and are automatically dereferenced when expanded by clicking the + in the displayed hierarchy. Note in the screen shot that expanding a pointer to a composite type does not always display what you want. For example, expanding the ramp2 pointer only displays the first element in the array, unlike the similar ramp explicit array, which displays each of its elements. This is because the debugger has no information about the length of an array referenced solely by pointer. In special cases, the debugger knows how to deal with that problem, as in the text pointer to a null-terminated character array, which correctly displays the contents of the array as a readable character string.

Arrays referenced by pointer: To display the elements of an array referenced by pointer, go to the Watch pane, click on a blank line in the Name column, and enter the name of the pointer followed by a comma and the number of elements you want to see:

Note that entering a number of elements greater than the actual length of the referenced array displays values beyond the end of the array.

Alternate ways to examine data at runtime

Stream output: Sometimes stepping and displaying values becomes inefficient. An example would be where you need to see the values of many variables as they change during loop iteration and relate later values to earlier values. In these cases, you can instead write the values to the cout or cerr streams:

cout << "iteration = " << loopIndex << " " << pixelCount << " " << row << " " << column << " " << " " << color << endl;

See Starting/Stopping/Stepping for notes on viewing this stream output. The output in the console (DOS) window can be selected by dragging the mouse over it and copied to the clipboard by pressing Enter. Be aware that outputting thousands of debug information lines in this way can dramatically slow down the execution of your program.

File output: Writing large amounts of debug information to a text file can be more efficient, but is more difficult. It is generally not worth the trouble of opening and managing a file object from within your code. An alternate approach is to have your program generate stream output as described above and redirect the output to a file. To do this, open a console (DOS) window and change to the Debug directory under your project directory. Then run your program from this console window (outside of the Visual Studio) with the command:

<project name> > outfile.txt to append to outfile if it exists

<project name> >> outfile.txt to overwrite outfile if it exists

  Tip: Choose main menu Debug -> Windows to display more runtime information than you'll ever need.

ws to display more runtime information than you'll ever need.