2.9. Bugs and Basic Program Debugging

bug (definition), debugging, syntax error, assembly error, linker error, loader error, logical error, run-time error, task synchronization error, synchronization error, Heisenbug
Review
"The term bug to describe a defect has been engineering jargon since at least as far back as the 1870s, long before electronic computers and computer software. ...

"U.S. Navy Rear Adm. Grace Hopper, a computer pioneer, popularized a story about a moth that caused a problem in an early electromechanical computer. While Hopper was working on the Mark II and Mark III as Harvard faculty in about 1947, operators traced an error in the Mark II to a moth trapped in a relay. The moth was removed from the mechanism and taped in a log book with the note 'First actual case of bug being found.'"
wikipedia.org

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. Assembly (linker/loader)
  3. Logical
  4. Run-time
  5. Task synchronization
  6. Heisenbug
Categories of programming errors (bugs). For convenience, computer scientists categorize programming errors, colloquially called "bugs," based on when and how they appear during software development or use. The text considers only the first four errors. Syntax and linker/loader 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, the last part of the compiler system, detects errors preventing the creation of a final executable program. The compiler system cannot detect logical and run-time errors, making them more difficult to locate. Most modern IDEs have a built-in debugger, which helps locate these errors.

This section treats the first two errors, while the following sections address logical and run-time errors.

Syntax Errors

syntax (definition), syntax error (definition), keyword, symbol, identifier (definition), diagnostic, error message, g++, Visual Studio

A program consists of a sequence of keywords, symbols1, and identifiers (an identifier is a name that a programmer gives to a programming element such as a variable, function, or class). The rules specifying the correct and acceptable order of the keywords, symbols, and identifiers form a programming language's syntax. Syntax must be flexible enough to let programmers write programs to solve new problems, while being well-defined enough for the compiler to translate programs that adhere to it into machine code that computers run. Any deviation from the syntax rules represents a syntax error.

The C++ compiler component detects syntax errors and displays diagnostic messages while compiling a program. However, the helpfulness of the diagnostics depends on the specific error and on the compiler. Sometimes, the error messages are very specific and accurate, but other times they only indicate that the compiler detected an error earlier in the program. To illustrate compiler diagnostics, the following examples deliberately omit the semicolon at the end of a statement.

#include <iostream>
using namespace std;

int main()
{
        cout << "Hello, World!" << endl
        return 0;
}
dab@Neptune:/tmp$ g++ hello.cpp
hello.cpp: In function 'int main()':
hello.cpp:6:40: error: expected ';' before 'return'
    6 |         cout << "Hello, World!" << endl
      |                                        ^
      |                                        ;
    7 |         return 0;
      |         ~~~~~~
(a)(b)
Command-line compiler diagnostic message. Command-line compilers, illustrated by g++, lack the dynamic syntax checking incorporated into some IDEs, but provide sufficient information to locate (and often identify) syntax errors.
  1. A trivial but frequent syntax error.
  2. The diagnostic message includes the function and file name (red), the line and column numbers (green), the syntax error (blue), and a caret (aka circumflex) pointing to the error location. (All highlighting is added for display.) In this example, the diagnostics underline the "return" statement, indicating where the compiler stopped checking syntax. A real program typically consists of multiple long files, making line numbers and other location information invaluable for fixing syntax errors.

 

A screen capture showing the 'return' statement underlined with a red wavy line - the line above is missing the semicolon at the end. The screen capture of the editor window shows a pointer indicating the line where the compiler detected the error.
(a)(c)
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 the syntax error: missing ';' before 'return'.
(b)
IDE error diagnostics. The screen captures from Microsoft's Visual Studio illustrate diagnostics appearing in the editor and output windows.
  1. In addition to the compiler diagnostics, many IDEs also provide a dynamic syntax checker that runs in the background, continuously checking a program's syntax. IntelliSense, part of the Visual Studio IDE, underlines the "return" statement, indicating where it stopped checking syntax.
  2. Attempting to compile or build a project with a syntax error causes the compiler to generate a diagnostic message in the output window. Although programmers universally place the terminating semicolon at the end of a statement, the diagnostic reports the error on line 7. Depending on the size of the output window, programmers may need to scroll to the top to see the source of the first syntax error. The figure adds the red ellipses for clarity, but the IDE adds the blue shading when programmers select the line.
  3. To help locate the error in large, authentic programs, double-click on an error in the output window (b). Studio displays the file containing the error, moves the cursor to the line where the error is detected, and marks the reported line with a pointer (circled in red).

Debugging Syntax Errors

syntax error, debugging, Bad Tuna

Syntax errors are necessarily the first errors programmers must correct; fortunately, they are (generally) the easiest to locate and identify. For example, the diagnostics above clearly identify the syntax error as a missing or expected ';' before 'return'. However, some compiler systems occasionally produce unhelpful diagnostics, while others are misleading, and some are utterly incomprehensible! In the early 1980s, I witnessed a C compiler that "helpfully" reported an error as: "Bad Tuna." (The compiler was an early university product, and, I suspect, the message was intended to help the compiler-writer - who neglected to remove the output before releasing the compiler - debug the compiler itself rather than application programmers using it.) Follow these guidelines to locate and correct syntax errors effectively.

Use Syntax Diagnostic Information Effectively

syntax error, Visual Studio, output window,

The diagnostics illustrated in the previous figures include the file name and line number where the compiler detects the error, not necessarily where it occurs. This information is of limited value for small programs, but it becomes very helpful as programs grow and span multiple files. All "good" programming editors provide at least one way to navigate to the reported location quickly. However, the compiler reports where it first locates the error. The compiler can detect some syntax errors - such as a missing semicolon - very quickly, but may fail to detect other errors until it has processed many lines of code. Consequently, the error may be many lines above the reported location, but it is never below the reported line. Therefore, the most efficient strategy is to begin on the reported line and work backward until you locate the error.

Be Aware of Syntax Error Recovery

syntax, syntax error, error recovery

The syntax elements - keywords, symbols, and identifiers - act like signposts during the compilation process. While the compiler finds the correct syntax elements where it expects them, it "knows where it is." However, it can "get lost" when there is a syntax error - a missing or incorrect "signpost." While the compiler is lost, it cannot validate the program's syntax, so it skips ahead. Sometimes it "recognizes" a major signpost (like the beginning of a function) and "can figure out where it is." Once it "knows where it is," it resumes validating the program's syntax - a compiler feature called error recovery. Error recovery doesn't correct the error, nor does the compiler generate additional machine code - only the syntax checker recovers from the error and continues validating the program's syntax from the recovery point onward.

Although error recovery produces additional validation and diagnostic information, it also creates a potential pitfall for new programmers. In some cases, a programmer may correct a syntax error and recompile a program only to see the number of syntax errors increase. The increase happens because the compiler processes code it previously skipped when it became "lost" after encountering the now-corrected syntax error. So, assuming the correction is valid, the increased error count still represents progress towards a syntactically correct program.

Recognize Compilation's Impact On Syntax Correction

syntax error, one-pass compiler

C++ utilizes a one-pass compiler that opens each file one at a time and processes its contents once, from beginning to end. Unsurprisingly, it displays syntax diagnostics in the order it detects them. However, a single syntax error can trigger multiple diagnostic messages, sometimes interleaving diagnostics from subsequent syntax errors. Consequently, it is most efficient to correct syntax errors in the order they are displayed. Depending on the compiler and operating system, you may need to scroll the output or console window, or use a command-line utility such as more to find the first diagnostic messages. Depending on the errors (and with experience), you can fix multiple errors at once, but initially it's best to fix problems one at a time.

Understand Syntax, Don't Rely Obscure Error Codes

syntax error, syntax error codes, Visual Studio

Some compilers, Visual Studio, for example, include obscure error codes in the diagnostic messages. Students report fruitless online searches for these codes, wasting time and effort. Furthermore, as the following figure illustrates, the codes change over time. So, even a successful search may be more confusing than helpful if it's out of date. A superior and more efficient approach is a thorough understanding of C++ syntax.

cout << "Hello World" >> endl;
(a)
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>'
(b)
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
(c)
The evolution of error messages. Different compiler systems produce widely different error messages. Even different versions of the same compiler can produce strikingly different diagnostics. You can see this evolution with Microsoft's Visual Studio compiler by introducing the highlighted error in the Hello World program. VS displays the illustrated error messages on a single line, but the figure folds them to improve readability.
  1. A simple syntax error that, at one time, produced 100 lines of error output - the maximum number before the compiler aborted - without ever actually telling the programmer the cause of the error. (Don't ask how I know this!)
  2. VS version 2015 includes the error code C2784 in the diagnostic.
  3. VS version 2019 uses the error code C2676 for the same syntax error.

Assembly (Linker/Loader) Errors

assembly error, linker error, loader error, busy file (definition), LNK error, Visual Studio

The linker or loader is the final stage of the full C++ compiler system. It assembles the application program object files, any used library functions, and a run-time file into the final executable program. Assembly errors occur when the linker/loader cannot complete the assembly process and fails to create the executable. (It's more common to refer to these as link, linker, or loader errors; however, the text adopts "assembly" to generalize the error to various operating systems.) This section addresses a relatively minor problem that causes an assembly error.


  1. As new disciplines evolve, they often develop their own vocabulary, which can create confusion as terms take on new, specialized meaning, while retaining their common, general usage. As used here, "symbol" means the non-alphabetic characters found on computer keyboards. Computer scientists also use "symbol" to refer collectively to various named programming elements, including variables, symbolic or named constants, functions, classes, etc.