2.9. Bugs and Basic Program Debugging

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

If debugging takes bugs out of a program, programming must put bugs into a program. There are many different kinds of bugs that programmers can and do put into their programs:

  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 while being sufficiently well-defined pattern that the compiler can translate programs 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.

The screen capture illustrates deliberately creating a syntax error by removing a semicolon at the end of the cout statement. 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 scrolling the output to the top. It shows the file name and line number where the syntax error was detected and displays syntax error : missing ';' before 'return'.
(c)
The screen capture of the editor window shows a pointer indicating the line where the compiler detected the error.
(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 reported line 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. Depending on the error, with experience, you can 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 error's actual location. 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.
  4. The keywords, symbols, and identifier patterns form a language's syntax, acting 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 it can "get lost" when there is a syntax error. When the compiler is lost, it cannot syntax check the code that it reads. Eventually, the compiler will "see" a major signpost (like the beginning of a function) and "figures out where it is." Once it "knows where it is," it resumes syntax checking the program but discontinues generating machine code. 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 who 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 this 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 occasionally produce unhelpful diagnostics, some that are misleading, and some that are utterly incomprehensible! In the early 1980s, I witnessed an error message from a C compiler that "helpfully" reported an error, "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 readability.
  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 accurately locates the error's position - both line (6) and column (26) - 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 many problems cause link errors, only one occurs frequently in the small programs appearing in the first few textbook chapters. It 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: