A common way of keeping track of what is going on in an application during the debugging process is to use printf statements in code such as the following:
#ifdef _DEBUG if ( someVar > MAX_SOMEVAR ) printf( "OVERFLOW! In NameOfThisFunc( ), someVar=%d, otherVar=%d.\n", someVar, otherVar ); #endif
The _ASSERT, _ASSERTE, _RPTn, and _RPTFn macros defined in the CRTDBG.h header file provide a variety of more concise and flexible ways to accomplish the same task. These macros automatically disappear in your release build when _DEBUG is not defined, so there is no need to enclose them in #ifdefs. For debug builds, they provide a range of reporting options that can be directed to one of several debugging destinations. The following table summarizes these options:
Macro | Reporting Option |
---|---|
_ASSERT | If an asserted expression evaluates to FALSE, the macro reports the file name and line number of the _ASSERT, under the _CRT_ASSERT report category. |
_ASSERTE | Same as _ASSERT, except that it also reports a string representation of the expression that was asserted to be true but was evaluated to be false. |
_RPTn (where n is 0, 1, 2, 3, or 4) |
These five macros send a message string and from zero to four arguments to the report category of your choice. In the cases of macros _RPT1 through _RPT4, the message string serves as a printf-style formatting string for the arguments. |
_RPTFn (where n is 0, 1, 2, 3, or 4) |
Same as _RPTn, except that these macros also include in each report the filename and line number at which the macro was executed. |
Asserts are used to check specific assumptions you make in your code. _ASSERTE is somewhat more convenient to use because it reports the asserted expression that turned out to be false. Often this tells you enough to identify the problem without going back to your source code. A disadvantage, however, is that every expression asserted using _ASSERTE must be included in the debug version of your application as a string constant. If you use so many asserts that these string expressions take up a significant amount of memory, you may prefer to use _ASSERT instead.
Examining the definitions of these macros in the CRTDBG.h header file can give you a detailed understanding of how they work. When _DEBUG is defined, for example, the _ASSERTE macro is defined essentially as follows:
#define _ASSERTE(expr) \ do { \ if (!(expr) && (1 == _CrtDbgReport( \ _CRT_ASSERT, __FILE__, __LINE__, #expr))) \ _CrtDbgBreak(); \ } while (0)
If expr
evaluates to TRUE, execution continues uninterrupted, but if expr
evaluates to FALSE, _CrtDbgReport is called to report the assertion failure. If the destination is a message window in which you choose Retry, _CrtDbgReport returns 1 and _CrtDbgBreak calls the debugger (via DebugBreak).
A single call to _ASSERTE could be used to replace the printf code at the beginning of this topic:
_ASSERTE(someVar <= MAX_SOMEVAR);
If _CRT_ASSERT reports were being directed to the console, then program execution would be interrupted when someVar
exceeded MAX_SOMEVAR
.
Asserts can also be used as a simple debugging error-handling mechanism for any function that returns FALSE when it fails. For example, in the following code, the assertion will fail if corruption is detected in the heap:
_ASSERTE(_CrtCheckMemory());
The following memory-checking functions can be used in asserts of this kind to verify pointers, memory ranges, and specific memory blocks:
The printf code at the start of this topic reported actual values of someVar
and otherVar
to stdout. If these values were useful in the debugging process, one of the _RPTn or _RPTFn macros could be used to report them. The _RPTF2 macro, for example, is defined essentially as follows in crtdbg.h:
#define _RPTF2(rptno, msg, arg1, arg2) \ do { \ if (1 == _CrtDbgReport(rptno, __FILE__, \ __LINE__, msg, arg1, arg2)) \ _CrtDbgBreak(); \ } while (0)
The following call to _RPTF2 would report the values of someVar
and otherVar
, together with the file name and line number, every time the function that contained the macro was executed:
_RPTF2(_CRT_WARN, "In NameOfThisFunc( ), someVar= %d, otherVar= %d\n", someVar, otherVar);
Of course, you may only be interested in knowing the values of someVar
and otherVar
under the circumstance that someVar
has exceeded its maximum permitted value. By using an assert, as described above, you could halt program execution and then use the debugger to examine the values of these variables. Alternatively, you could use a variation of the original printf code, enclosing a conditional call to the _RPTF2 macro in #ifdef
s:
#ifdef _DEBUG if (someVar > MAX_SOMEVAR) _RPTF2(_CRT_WARN, "In NameOfThisFunc( ), someVar= %d, otherVar= %d\n", someVar, otherVar ); #endif
If you find that a particular application needs a kind of debug reporting that the macros supplied with the C runtime library do not provide, you can write a macro designed specifically to fit your requirements. In one of your header files, for example, you could include code like the following to define a macro called ALERT_IF2:
#ifndef _DEBUG /* For RELEASE builds */ #define ALERT_IF2(expr, msg, arg1, arg2) ((void)0) #else /* For DEBUG builds */ #define ALERT_IF2(expr, msg, arg1, arg2) \ do { \ if ((expr) && \ (1 == _CrtDbgReport(_CRT_ERROR, \ __FILE__, __LINE__, msg, arg1, arg2))) \ _CrtDbgBreak( ); \ } while (0) #endif
One call to ALERT_IF2 could perform all the functions of the printf code at the start of this topic:
ALERT_IF2(someVar > MAX_SOMEVAR, "OVERFLOW! In NameOfThisFunc( ), someVar=%d, otherVar=%d.\n", someVar, otherVar );
This approach can be particularly useful as your debugging requirements evolve, because a custom macro can easily be changed to report more or less information to different destinations, depending on what is more convenient.