Using asserts

An assert checks that a condition is true. The assertion means just that: the program should not continue if the condition is not true. Clearly asserts should not be called in release code and hence conditional compilation must be used. Asserts should be used to check for conditions that should never happen: never events. Since the conditions do not happen there should be no need for asserts in release builds.

The C Runtime provides the assert macro that is available through the <cassert> header file. The macro, and any functions called in the expression passed as its only parameter, will be called unless the NDEBUG symbol is defined. That is, you do not have to define the _DEBUG symbol to use asserts and you should have taken extra action to explicitly prevent assert from being called.

It is worth re-iterating this. The assert macro is defined even if _DEBUG is not defined, so an assert could be called in release code. To prevent this from happening you must define the NDEBUG symbol in a release build. Conversely, you can define the NDEBUG symbol in a debug build so that you can use tracing but do not have to use asserting.

Typically, you will use asserts in debug builds to check that pre- and post-conditions are met in a function and that class invariant conditions are fulfilled. For example, you may have a binary buffer that has a special value at the tenth byte position and so have written a function to extract that byte:

    const int MAGIC=9; 

char get_data(char *p, size_t size)
{
assert((p != nullptr));
assert((size >= MAGIC));
return p[MAGIC];
}

Here, the calls to assert are used to check that the pointer is not nullptr and that the buffer is big enough. If these asserts are true, then it means that it is safe to access the tenth byte through the pointer.

Although it is not strictly necessary in this code, the assertion expressions are given in parentheses. It is good to get into the habit of doing this because assert is a macro and so a comma in the expression will be treated as a macro parameter separator; the parentheses protected against this.

Since the assert macro will be defined in release builds by default, you will have to disable them by defining NDEBUG on the compiler command line, in your make file, or you may want to use conditional compilation explicitly:

    #ifndef _DEBUG 
#define NDEBUG
#endif

If an assert is called and it fails, then an assert message is printed at the console along with source file and line number information and then the process is terminated with a call to abort. If the process is built with release build standard libraries then the process abort is straightforward, however, if the debug builds are used then the user will see the standard Abort/Retry/Ignore message box where the Abort and Ignore options abort the process. The Retry option will use Just-in-Time (JIT) debugging to attach the registered debugger to the process.

In contrast, the _ASSERT and _ASSERTE macros are only defined when _DEBUG is defined, so these macros will not be available in release builds. Both macros take an expression and generate an assert message when the expression is false. The message for the _ASSERT macro will include the source file and line number and a message stating that the assertion failed. The message for the _ASSERTE macro is similar but includes the expression that failed.

    _CrtSetReportMode(_CRT_ASSERT, _CRTDBG_MODE_FILE); 
_CrtSetReportFile(_CRT_ASSERT, _CRTDBG_FILE_STDOUT);

int i = 99;
_ASSERTE((i > 100));

This code sets the reporting mode so that the failed assert will be a message printed on the console (rather than the default, which is the Abort/Retry/Ignore dialog). Since the variable is clearly less than 100, the assert will fail and so the process will terminate and the following message will be printed on the console:

    test.cpp(23) : Assertion failed: (i > 100)

The Abort/Retry/Ignore dialog gives the person, testing the application, the option of attaching the debugger to the process. If you decide that the failure of the assertion is heinous you can force the debugger to attach to the process by calling _CrtDbgBreak.

    int i = 99; 
if (i <= 100) _CrtDbgBreak();

You do not need to use conditional compilation because in release builds the _CrtDbgBreak function is a no-operation. In a debug build, this code will trigger JIT debugging, which gives you the option to close the application or launch the debugger, and if you choose the latter, the registered JIT debugger will be started.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset