Debugging Tips

Debugging a project starts with the coding and ends only after the product is delivered; and, irrespective of project success or failure, debugging cannot be avoided. However, familiarity with good debugging methods, best practices, and use of debugging tools are prerequisite for a successful project. In this article, I have listed a few good practices for debugging that I have learned during my development career. If you are looking for quick debugging tips, specifically for Windows-based projects, refer to the General Tips section.

These debugging tips are directly applicable to C and C++ projects. However, in general, these are also applicable for any software project.

Compiler Warnings

Make sure your code is free from compiler warnings. Even a single warning has potential of consuming several hours of debugging. If you have access to lint, use it. It will improve the quality of the code tremendously. If you are using VC++, use the /W4 switch to work the compiler for you to give extended warnings. Study each extended warning carefully to make sure that it is not a potential bug.

Using Asserts

Assert is a MACRO which, when it fails, stops the program execution (it also may raise an exception or break in the debugger, depending on the configuration and the programming language being used). It is enabled only in the Debug version of the build and does not have any effect in the released builds.

Assert can be used as an effective debugging aid. Use Assert to check for inconsistencies, preconditions, null pointer checks, and so on. It can save you a lot of time by locating most of the programming mistakes and logic errors during unit testing.

Usually, there is confusion regarding the use of Assert versus error handling and people use both interchangeably. The simple rule is that Assert should be used to check for programming errors, logical mistakes, and precondition checks, but not for run time error checks. For example, if I have written a function that takes a pointer as input and I do not expect it to be NULL, I should use Assert to check that the pointer is not null. The user of this function should do error handling so that he does not pass a null pointer to this function. Consistent use of this principle results in code that is simple and readable, avoids bloating of code due to unnecessary error handling, and produces a robust product. It is explained in the following example.

VC++ defines Assert as ASSERT. VC++ also defines VERIFY MACRO, which is the equivalent of ASSERT but also works in Released builds.

Trace Logging

Trace logging is a mechanism to log program messages to a file, console, or both.

Debuggers are good at locating the source of errors during the early phase of development. But, it is not wise to depend only on a debugger as the project moves towards its later phases. There are several reasons for this. For example, sometimes it is more efficient to look at the trace messages and find the problem. In my experience, if a program logs proper tracing messages, in general, I find using trace messages more efficient in locating the source of the problem. However, putting proper tracing messages takes time but pays off tremendously after the coding is completed. The second reason for using tracing is that quite often, the problem reported may not be reproduced when debugger is used. It happens mainly when the program is multi threaded or its execution flow depends on other asynchronous events, such as input from network devices, and so forth. The third reason is that it is very helpful in locating the problem source of the client-reported problems. When your client reports a problem to you, most often this is the only trick that is going to help you. And believe me, if you have proper tracing mechanism built into the product, you and your client are going to be happy about it. Due to these reasons, it is a de facto standard to have some kind of trace logging functionality in the product.

However, to make the tracing useful, it should fulfill the following requirements:

It should produce detailed diagnostic messages when needed.

It should be possible to filter out non-interesting messages.

The first requirement is based on the fact that you need detailed information to find the source of problem. However, as the program produces more and more trace logs, it becomes more and more difficult to find interesting messages. It is my experience that if a program produces indiscriminate tracing logs, it becomes ineffective in problem solving, if proper filtering mechanism is not present. The previous two requirements seem to contradict each other and there should be some mechanism to balance them. These are fulfilled by the following two principles:

It should be possible to disable and enable tracing at the module level. It means that I can disable the tracing for other modules I am not interested in. This also can be achieved by logging trace messages for modules in different files or consoles.

There should be a facility to filter tracing based on some generic categories. For example, in the early phase of module development, I might be interested only in flow of my module and later only in errors. I use following filtering categories in my projects:

TRACE—Enabled when I want to just see the flow of program. It prints messages at entry and exit of a function.

INFO—Used to print important events in program.

DEBUG—Used to enable debug-tracing statements. These are usually printed to see the state of the program.

ERROR—Used to print error conditions in the program.

FATAL—Used to print a fatal event. This usually results in program termination.

To best utilize this mechanism, it should be possible to configure trace filtering at run time or at least at the start of the program. It is a time killer if the program needs to be compiled to reconfigure the filtering mechanism.

MS Windows has functionality for sending trace outputs to Debug Monitors such as DBMON and DebugView. This is done either by using the DebugOutputString API or the VC++ TRACE macro. However, beware that TRACE is enabled only in DEBUG builds. DebugOutputSting and TRACE do not implement any filtering mechanism; you need to implement a filtering mechanism, if needed. Using this has the following two disadvantages:

To capture the trace output, Debug Monitor should be running.

It usually slows down the performance of the application, when the Debug Monitor is capturing the debug trace.

It has the advantage that you do not need to implement trace-logging functionality and there are good freeware Debug Monitors available for use. This mechanism is not suited for large projects and I advise you to implement a file trace logging mechanism for medium to large projects.

Master Your Debugger

Chances are that you are going to use the debugger very often. Today's debuggers are very powerful and can save you a lot of time if used properly. The VC++ debugger has numerous options, including advanced options to check memory leaks, finding memory corruptions, and so on. Check MSDN for details.

Use Memory Tools

Use of tools can improve productivity and quality tremendously. For C/C++ programs, it is very difficult to find memory leaks and corruptions without using proper tools and can take days whereas tools can detect them instantly. There are a lot of commercial and freely available tools that can be used. I have used MPATROL, an open source library, satisfactorily.

Incremental Testing

Write and test your programs incrementally. Try to write small procedures and test them immediately if possible. Working with small increments reduces complexity. Also, use a unit testing tool/framework from start. Using a unit testing tool/framework simplifies incremental testing. There are good open source unit test frameworks available for both C (CuTest) and C++ (CppUnit).

Top White Papers and Webcasts

Live Event Date: March 19, 2015 @ 1:00 p.m. ET / 10:00 a.m. PT
The 2015 Enterprise Mobile Application Survey asked 250 mobility professionals what their biggest mobile challenges are, how many employees they are equipping with mobile apps, and their methods for driving value with mobility.
Join Dan Woods, Editor and CTO of CITO Research, and Alan Murray, SVP of Products at Apperian, as they break down the results of this survey and discuss how enterprises are using mobile application management and private …

Remember getting your first box of LEGOS as a kid? How fun it was putting the pieces together, collaborating with your friends to create something new? Now, as an IT professional, assembling and maintaining a Lego-like collaboration infrastructure isn't what you signed up for. Piecing together disparate systems of record for email, web meetings and other applications is about as painful as stepping on a pile of Legos. Download the e-book to learn how implementing a collaboration system connects systems of …