Programs that contain run-time errors compile, but they "crash" or fail to end when they run. If a program crashes, the system may display a diagnostic that helps programmers locate the error, but a program that fails to terminate produces no diagnostics. In either case, locating the error is the first step programmers must take to identify and correct it. Run-time errors are typically a sub-class of logical errors, which suggests that programmers locate them by instrumenting the code or using a debugger.
Fortunately, none of the programming operations introduced in the first chapters are likely to cause a run-time error. However, this situation makes it difficult to demonstrate a run-time error by causing one. So, the following examples rely on trivial pseudocode to illustrate the debugging techniques.
Run-time errors are difficult to locate because they run many statements before unexpectedly experiencing a catastrophic failure. Programmers use this same behavior to locate the error. If run-time errors are a sub-class of logical errors, then it seems reasonable to locate them using a similar strategy. Step 2 of the logical-error-location strategy is, "Trace the calculations through program execution." While intermediate values may help programmers correct a run-time error, the primary goal is locating the statement causing it. To locate a run-time error, programmers bracket it with tracing operations implemented with instrumentation statements or a debugger. Initially, the tracing operations may surround many programming statements, but are moved closer together, narrowing the search.
While the strategy describes bracketing a run-time error with two tracing operations, programmers are not limited to only two. Sometimes, especially at the beginning of the process, it's desirable to use multiple operations spread throughout the suspect code. Regardless of the number of operations, programmers apply the strategy to adjacent pairs.
While the values of variables and other expressions may ultimately help debug a run-time error, locating it is the initial step. To trace program execution, programmers insert statements that signal, "The program made it this far before failing." So, the only requirement of the instrumented code's output is that it uniquely identifies a reached location in the program. I suspect that this was the purpose of the "Bad Tuna" message described previously.
|
statement 1; |
|
| (a) | (b) | (c) |
The logic and bracketing process used to locate run-time errors is independent of how programmers perform the process, implying they can use either manual instrumentation or a debugger. Nevertheless, a debugger automates and simplifies the task, and has the advantage that removing the tracing operations is fast and easy. Programmers implement tracepoints in Visual Studio as illustrated in Figure 10 of the previous section, or they can leverage tracepoints' status as specialized breakpoints as illustrated in the following figure:
![]() |
Programmers create a breakpoint by left-clicking in the grey column next to a statement, then convert it to a tracepoint by right-clicking the circle or by hovering the mouse pointer over the circle and pressing the "gear" button in the pop-up menu. |
![]() |
Checking the "Actions" checkbox converts a breakpoint to a tracepoint. Programmers can enter labels such as "Start" or "Done" or evaluate expressions as illustrated in the previous section. Typing a "$" in the input field displays another menu. Two predefined action variables, $FUNCTION and $FILEPOS, display the file's full name and the statement's line number in the tracepoint output. Tracepoints must be attached to an executable statement, so the example displays line numbers 26 and 32. |
![]() |
Following the Figure 1 strategy, programmers bracket blocks of code suspected of containing a run-time error with tracepoints, and move the tracepoints together to locate the error. Like breakpoints, programmers remove all tracepoints with the menu "Debug → Delete All Breakpoints" or the keyboard shortcut Ctrl-Shift-F9. |