Lesson 3: Introduction to Debugging

This lesson presents a general introduction to the Visual C++ integrated debugger. Coverage of the extensive subject of debugging is best begun with an overview, rather than with an immediate jump into specifics about the debugger. After attaining a solid foundation about the debugger's purpose, as well as its advantages and limitations, you will be better prepared to use the debugger, which is described in the next lesson.

After this lesson, you will be able to:

  • Understand the general manner in which a debugger works.
  • Understand specific features of the Visual C++ integrated debugger.
  • Use various macros, provided by the MFC framework that facilitate the task of debugging.
Estimated lesson time: 20 minutes

What Is a Debugger?

Debugging is a general term for finding and correcting program logic errors. It is necessary to master the art of debugging to develop applications successfully. A debugger is an application that facilitates the debugging process. The debugger runs a program under tight control, and can freeze operations at any point to allow you to check the program's status during execution.

The Visual C++ debugger is incorporated into the Visual Studio environment, and has its own menu and toolbar commands. Before using the debugger, you must create a debug build of your project. You cannot begin using the debugger until your code compiles into an executable file without syntactic error.

  • To create a debug build
    1. Right-click in the toolbar area and display the full Build toolbar (not the Build minibar). On the Build toolbar, make sure the Win32 Debug build configuration is selected as shown in Figure 13.2.
    2. click to view at full size.

      Figure 13.2 The Win32 Debug build configuration

    3. On the same toolbar, click the Build button.

    A debug operation consists of several steps, typically beginning in the text editor. To begin the debug operation, identify in the source code of a failing program the section where you suspect the problem originates, and then mark the first instruction of that section. Start the debugger, which executes the program until control reaches the mark you set at the start of the questionable section. When the debugger suspends the program's execution, you can then step through each instruction. This process gives you the opportunity to check current data values while the program is suspended, and ensure that the flow of control is proceeding along an expected path.

    Debug vs. Release

    The debug version of a product is the version with which you work during development and testing while you strive to make the program error-free. The debug version contains symbol information that the compiler places in the object file. By reading both the original source files and the project's symbol information, the debugger can associate each line of the source code with the corresponding binary instructions in the executable image. The debugger runs the executable but uses the source code to show the program's progress.

    NOTE
    Code that has been optimized by the compiler can cause the debugger to behave in an unexpected manner. If strange errors occur when you step through code in the debugger, check that the debug configuration of your project has compiler optimization set to unavailable. Optimization is set on the C/C++ page of the Project Settings dialog box.

    The release version of a product is a more tightly compiled version that is distributed to your customers. The release version contains only executable in-structions optimized by the compiler, without the symbol information. You can execute a release version inside the debugger; however, if you do, the debugger informs you that the file has no symbol data. Conversely, you can execute the debug version of a program normally, without the debugger. These execution processes have practical consequences as a result of a Visual C++ feature known as just-in-time debugging. If the program encounters an unhandled exception, the system's SEH mechanism causes control to be returned to Visual C++, which then executes the debugger. The debugger shows the instruction that caused the fault and displays data values as they existed when the program stopped.

    MFC Debug Macros

    MFC provides several macros that serve as debugging aids and affect only debug builds. We have already examined the TRACE macro and its variations, and seen how it's possible to use TRACE to log a program's actions and state. This section examines several other MFC macros, including ASSERT and its variants VERIFY and DEBUG_NEW.

    The ASSERT macro offers a convenient way to test assumptions during application development without adding permanent error-checking code. For example, you might want to check a pointer for a non-zero value before using it. One way you can perform this action is to add a specific if test, as demonstrated in the following code for a function that accepts a CButton pointer as its parameter:

     void SomeFunction(CButton* pbutton) {      if (pbutton)      {          // Use the pbutton pointer          ...      } }

    While such checks are good programming practices that help avoid potential problems, they might no longer be warranted in a program's release version. Including hundreds of such checks throughout a program can add unnecessary code to the finished product—code that increases the program's size and slows its execution. This unneeded code can be avoided by using an ASSERT macro, as demonstrated in the following code:

     void SomeFunction(CButton* pbutton) {      ASSERT(pbutton);   // Make sure that pbutton is non-zero      // Use the pbutton pointer      ... }

    If the expression passed to ASSERT evaluates to zero, the program halts with a system message alerting you to the problem. As shown in Figure 13.3, the message gives you the choice of terminating the application, ignoring the problem, or debugging the application to learn more about the problem.

    Figure 13.3 ASSERT failure message

    The ASSERT macro creates code only for debug builds and evaluates to nothing in release builds. You can therefore insert hundreds of ASSERT statements in your code without increasing the size of the final product. If you prefer to leave a check in place for both debug and release builds, use the VERIFY macro instead, which functions regardless of the build configuration.

    ASSERT can be used with any expression that yields a Boolean result, such as ASSERT(x < y) and ASSERT(i > 10), but its variants are more specialized. The similar ASSERT_VALID macro, for example, is used only with pointers to an object derived from the MFC CObject class. This macro does everything ASSERT does, but also calls the object's AssertValid member function to verify the object.

    The ASSERT_KINDOF macro applies only to pointers to objects derived from CObject. ASSERT_KINDOF tests that the pointer represents an object of a specified class, or is an object of a class derived from the specified class. For example, this line verifies that pDoc points to an object derived from CDocument:

    ASSERT_KINDOF(CMyDocument, pDoc)

    The DEBUG_NEW macro helps you find memory leaks that can result when a new statement is not properly matched to a delete statement. The macro is easy to use and requires the addition of only a single line to your source code:

    #define new DEBUG_NEW

    With this definition in place, the compiler interprets every new statement in your code as DEBUG_NEW, and MFC takes care of the rest. For debug builds, DEBUG_NEW keeps track of the file name and line number where each new statement occurs. Your program can then call the CMemoryState:: DumpAllObjectsSince() member function, which displays a list of new allocations in the Visual C++ Output window, together with the file name and line number where the allocation occurs. This type of information can aid in pinpointing leaks. In release builds, DEBUG_NEW simply resolves to new.

    Lesson Summary

    This lesson introduced the concepts of the important process of debugging, in which the developer locates and corrects logical errors in an application. It described in general terms how a debugger works and the differences between release and debug versions of a project, and explained some of the macros that MFC provides as aids to debugging.

    The ASSERT macro provides an effective means of alerting you to errors without becoming a permanent part of your program's release version. Its variants ASSERT_VALID and ASSERT_KINDOF are used with pointers to objects derived from the MFC CObject class. The ASSERT macro is used with a Debug build of an application. The VERIFY macro may be used with both debug and release builds. The DEBUG_NEW macro finds cases where free store memory is allocated but not properly returned to the system.



    Microsoft Press - Desktop Applications with Microsoft Visual C++ 6. 0. MCSD Training Kit
    Desktop Applications with Microsoft Visual C++ 6.0 MCSD Training Kit
    ISBN: 0735607958
    EAN: 2147483647
    Year: 1999
    Pages: 95

    flylib.com © 2008-2017.
    If you may any questions please contact us: flylib@qtcs.net