2.9. Bugs and Basic Program Debugging

Time: 00:09:14 | Download: Large, Large (CC), Small | Streaming, Streaming (CC) | Slides (PDF)

If debugging is the process of taking bugs out of a program, then programming must be the process of putting bugs into a program. There are many different kinds of bugs that programmers can put into a program:

  1. Syntax
  2. Link
  3. Logical
  4. Runtime
  5. Task synchronization
  6. Heisenbug

Only the first four kinds of bugs are considered this semester. Syntax and link errors are detected at compile-time by one part of the compiler system. Specifically, the compiler component, the middle part of the compiler system, provides diagnostic messages that help programmers locate and identify syntax errors. The linker (or loader on some platforms), the last part of the compiler system, detects link errors. The compiler system cannot detect logical and runtime errors, and they are typically much more difficult to locate. Most modern IDEs, including Visual Studio, have a built-in debugger, which is useful for locating these errors.

Syntax Errors

A correct program consists of a sequence of keywords, symbols, and identifiers (an identifier is a name that a programmer gives to a programming element such as a variable, function, class, etc.). The sequence must be flexible enough to allow programmers to write the various programs needed to solve new problems, but it must also follow a well-defined pattern that the compiler can translate into the machine code that a computer can run. Each programming language specifies the specific pattern that its compiler recognizes and accepts. The pattern forms the language's syntax. Any deviation from this pattern represents a syntax error.

Every compiler, specifically the compiler component, will detect syntax errors when compiling the program. However, the error messages or diagnostics will vary greatly between different compilers. Sometimes the error messages are very accurate, while at other times, they provide little more than a starting point for your debugging session. Additionally, some systems, such as Visual Studio, provide a dynamic syntax checker that runs in the background while you write code. Studio's syntax checker, IntelliSense, underlines potential syntax errors with a red wavy line. After correcting a syntax error, it may be necessary to "click" the mouse pointer to the end of the corrected statement to allow IntelliSense to "catch up" and remove the underlining - it is even sometimes necessary to save the file to trigger the update.

A screen capture indicating the semicolon at the end of the cout statement is going to be deliberately removed to create a syntax error. A screen capture showing the 'return' statement underlined with a red wavy line - the line above is missing the semicolon at the end.
(a)(b)
A screen capture showing the output window after it has been scrolled to the top. It shows the file name and line number where the syntax error was detected. It also displays: syntax error : missing ';' before 'return'.
(c)
A screen capture of the editor window with a pointer indicating the line on which the syntax error was detected.
(d)
Visual Studio error detection and reporting. An illustration of IntelliSense and the error output from Visual Studio.
  1. Intentionally creating a syntax error by removing a semicolon from the "Hello, World!" program.
  2. IntelliSense underlines the "return" statement - this is NOT the syntax error but is the point where IntelliSense detects the error.
  3. The output window (usually at the bottom of Visual Studio) after attempting to build the hello world program with a missing semicolon. The error is reported on line 7 but is actually at the end of line 6. Errors may occur above the line on which they are reported but are never below.
  4. Double clicking on an error in the output window (c) will cause Studio to display the file containing the error, will move the cursor to the line where the error is detected and will mark the reported line with a pointer (circled in red).

When Studio attempts to build a project (5:43) that contains syntax errors, it reports those errors in the "Output" window (Figure 1(c)), which is generally at the bottom of the Studio IDE. To most effectively use the information contained in the output window, follow these five "rules of thumb:"

Five Rules of Thumb: Using The Output Window Error Information

  1. One syntax error can be detected more than once, and it can cause multiple problems in the program. So it is always best to start at the top of the output window (i.e., with the first error) and to work down one error at a time. With some errors and with experience, you will be able to work with more than one error at a time, but for now, it's best to fix the problems one at a time.
  2. The fastest way to locate an error in the text editor is by the error message in the "Output" window at the bottom of Visual Studio (you may need to select the "Output" tab). Scroll to the error message describing the error you wish to correct next (the line highlighted in blue in Figure 1(c)) and double-click the message. The debugger moves the file containing the error to the front in the editor and marks the line where it detected the error with a pointer along the left edge (Figure 1(d)). This fast navigation "trick" is of little use with tiny programs like hello world but becomes very helpful as programs increase in size and spread over multiple files.
  3. The compiler reports where it first locates the error, which may not be the actual location of the error. So, begin on the line where the debugger reports the error. If the error is not on that line, then work backward (i.e., upward) one line at a time until you find the error. Note that the error will never be below the reported line number.
  4. The keywords and symbols forming a language's syntax act like guideposts during the compilation process. While the compiler finds the correct syntax elements where it expects them, the compiler "knows where it is," but the compiler can "get lost" when there is a syntax error. While the compiler is lost, it cannot check the syntax of the code that it is reading. Eventually, the compiler will "see" a major signpost (like the beginning of a function) and "figures out where it is" and resumes checking the syntax. The computer scientists that create compilers call this technique error recovery. The compiler doesn't recover in the sense that it corrects the error, but it does recover enough that it can continue checking some of the program. If you correct a syntax error, recompile the program, and see MORE errors than you did before the correction, DON'T put the error back - the compiler is just checking code that it previously skipped before it could recover.
  5. Save yourself some time: I have helped students in the past that have reported Googling the error number (e.g., C2143 or LNK2019) that appears as a part of the error message in the output window. You will rarely find more information doing than what the compiler reports in the output window. You will make better use of your time and effort learning how to read C++ and how to spot the details that are wrong or out of place.

In the example above, the error message clearly identifies the problem: Missing ';' before 'return'. However, some compiler systems produce diagnostics for some programming errors that are frequently misleading and are sometimes utterly incomprehensible! In the early 1980s, I witnessed an error message from a C compiler that helpfully said of a statement, "Bad Tuna." Fortunately, compilers continuously improve their error messages.

1>e:\tmp\cs1410 past\hello\hello\hello.cpp(6): error C2784:
'std::basic_istream<_Elem,_Traits> &std::operator >>(std::basic_istream<_Elem,_Traits> &&,_Ty &)' :
could not deduce template argument for 'std::basic_istream<_Elem,_Traits> &&' from 'std::basic_ostream<_Elem,_Traits>'
(a)
1>e:\tmp\cs1410\Hello\Hello\hello.cpp(6,26): error C2676: binary '>>':
'std::basic_ostream<char,std::char_traits<char>>'
does not define this operator or a conversion to a type acceptable to the predefined operator
(b)
The evolution of error messages. Different compiler systems produce widely different error messages. But even different versions of the same compiler can produce strikingly different diagnostics. You can get a sense of this evolution with Microsoft's Visual Studio compiler by making the highlighted error in the hello word program:
cout << "Hello World" >> endl;
Early version VS produced 100 lines of error output - the maximum number before the compiler aborted - without ever actually telling the programmer the cause of the error. New versions of Studio have become more helpful. Note that the error messages shown in (a) and (b) are displayed on a single line but are wrapped here to improve read ability.
  1. VS 2015: the compiler tells us the line (6) on which the error occurs but isn't very clear about what caused the problem. Fortunately, IntelliSense underlines the problem.
  2. VS 2019: the compiler gives us an accurate position - both the line (6) and the column (26) - where the error is located, and IntelliSense continues to underscore the error. Significantly, the error message is now much more clear.

Link (LNK) Errors

Link errors arise from the linker, the last component or program of the compiler system to run. Although there are many causes of link errors, only one is common when writing the programs appearing in the first few chapters. The problem represents a user error that is very easy to make but is also easy to correct. To demonstrate this problem, begin with the following simple and correct program:

#include <iostream>
using namespace std;

int main()
{
	int	number;
	cout << "Please enter a number: ";
	cin >> number;

	return 0;
}
  1. Build the program
  2. Run the program (without debugging)
  3. With the console window still open (i.e., don't enter a value at the prompt and don't press the enter key), make a non-significant change (e.g., add an empty line at the bottom of the program)
  4. Save and rebuild the program
  5. Run the program again
(a)(b)
1>------ Rebuild All started: Project: Error3, Configuration: Debug Win32 ------
1>C:\Program Files (x86)\MSBuild\Microsoft.Cpp\v4.0\V110\Microsoft.CppClean.targets(75,5):
	warning : Access to the path 'E:\TMP\CS1410 PAST\ERROR3\DEBUG\ERROR3.EXE' is denied.
1>C:\Program Files (x86)\MSBuild\Microsoft.Cpp\v4.0\V110\Microsoft.CppClean.targets(75,5):
	warning : Access to the path 'E:\tmp\cs1410 past\Error3\Debug\Error3.exe' is denied.
1>  Error3.cpp
1>LINK : fatal error LNK1104: cannot open file '****************.exe'
========== Rebuild All: 0 succeeded, 1 failed, 0 skipped ==========
(c)
A link error caused by a busy file.
  1. Code used to demonstrate a common link error.
  2. Steps to create the link error.
  3. The error message as it appears in the output window (the second and third lines are wrapped to improve how they display here). The highlighted line is the key to solving the problem: it states that it is a LINK error (also, the error number begins with LNK). Unlike syntax errors, link errors are not detected while processing the source code files, so you CANNOT double-click the error in the output window and jump to the error location in the editor. The last part of the error message states "cannot open file *.exe" (the file name is replaced by a asterisks as it will vary).

When the compiler system successfully compiles a program, the linker will create a new executable file whose name ends with a .exe extension. In this example, when the linker tries to create the new executable file it is unable to remove the old file because the program is busy (i.e., it is still running and in use). The solution is simply to close the previous console window before rebuilding the program. This error usually occurs when you iconify the console window while you continue to work on the code and then forget to close the window before recompiling the program.