Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Assert is your friend

0.00/5 (No votes)
11 Mar 2004 8  
How to use assert to find bugs in your programs

Introduction

It's not uncommon to see postings on the C++ message board asking for programming help where the problem is stated as being an unwanted assert error. Usually it's a request for help to get rid of the assert error but it's almost invariably framed as though the assert itself is evil.

I can quite understand the dismay that an assert error can cause to the new programmer. Here's your program doing, mostly, what you want, and then bang! an assert.

So let's examine asserts and why they're there and what we can learn from them. I should stress that this article discusses how MFC handles asserts.

Linguistics

From a google search ('define assert') and clicking on the web definition.
The verb "assert" has 4 senses in WordNet.

1. assert, asseverate, maintain -- (state categorically)
2. affirm, verify, assert, avow, aver, swan, swear -- (to declare or affirm solemnly 
                                                       and formally as true; "Before 
                                                       God I swear I am innocent")
3. assert, put forward -- (insist on having one's opinions and rights recognized; 
                           "Women should assert themselves more!")
4. insist, assert -- (assert to be true; "The letter asserts a free society")
All of the above meanings apply in this instance but meaning 4 is the most literally accurate in the context of asserts. An assert states that the condition being asserted is true. If it isn't true the program is in serious trouble and you, the programmer, should be warned of this.

What assert means in code

When a programmer writes an assert statement he's saying, 'this condition must be true or we have an error'. Suppose you're writing a function that expects to recieve a string pointer.
void CMyClass::MyFunc(LPCTSTR szStringPtr)
{
    if (*szStringPtr == '7')
        DoSomething();
}
The function reads the memory the pointer points to so it had better be pointing at valid memory. Otherwise you're going to crash! One of the many things that can go wrong when calling the function is if you were to pass it a NULL pointer. Now if it happens that the pointer is in fact NULL and your program crashes you don't have a lot of information to work with. Just a message box with a code address and the information that you tried to read memory at location 0x00000000. Relating the code address to the actual line in your code isn't a trivial task, especially if you're new to the game.

So let's rewrite the function a little.

void CMyClass::MyFunc(LPCTSTR szStringPtr)
{
    ASSERT(szStringPtr);

    if (*szStringPtr == '7')
        DoSomething();
}
What this does is test szStringPtr. If it's NULL it crashes right away. Hmmm... it crashes? Yes, that's right. But it crashes in a controlled way if you're running a debug build of your program. MFC has some built-in plumbing to take a controlled crash and connect it to a copy of the debugger. If you were running a debug build of this program and the assert failed you'll see a messagebox similar to this one.

Sample Image - imagepreviewdialog.jpg

This shows you which file and which line triggered the assertion. You can abort, which stops the program, or you can try to ignore the error, which sometimes works, or you can retry, which invokes the debugger and takes you right to the line that failed. This works even if you're running the program stand-alone provided there's a debugger installed on the computer.

Differences between release and debug builds

I used the phrase 'debug build' quite a few times in the preceding paragraphs. It's important to understand that asserts are a debug helper. If you're building a debug version of your program the compiler includes all the code inside the ASSERT(...). If you're building a release version the ASSERT itself and all the code inside the parentheses disappears. The assumption is that you've tested your debug builds and caught all likely errors. If you're unlucky and missed an error and release a buggy version of your program the hope is that it'll limp along even though it failed a test that an assert would have caught. Sometimes optimism is a good thing!

It might happen that you want, in a debug build, to assert that something is true and the something you're asserting is code that must be compiled into your program whether it's a debug build or a release build. That's where the VERIFY(...) macro comes to the rescue. VERIFY in a debug build includes the extra plumbing MFC provides to let you jump into the debugger in the event that the condition isn't satisfied. In a release build the extra plumbing is omitted from your program but the code inside the VERIFY(...) statement is still included in your executable. For example.

VERIFY(MoveFile(szOriginalFilename, szNewFileName));
will cause a debug assert if, for whatever reason, the MoveFile() function fails in debug builds. Regardless of what kind of build the MoveFile() call will be included in your program. But in a release build the failure of the call is simply ignored. Contrast this with
ASSERT(MoveFile(szOriginalFilename, szNewFileName));
In debug builds the MoveFile() will be compiled and executed. In release builds the line completely disappears and no file move is attempted. This can lead to some puzzled head-scratching.

Tracking down asserts in MFC

Now if you're reading this in the hopes of understanding assert errors the odds are that the assert you're debugging didn't come from your code. If you're lucky it came from MFC, for which you have the source code.

The first thing to remember is that it's not very likely to be caused by a bug in MFC. I don't deny the existence of bugs in MFC but in the dozen years I've been using MFC I've never had an ASSERT happen due to a bug in MFC.

The second thing to remember is that the ASSERT is there for a reason. You need to examine the line of code that triggered the assert, and understand what it was testing.

For example, quite a few classes in MFC are wrappers around Windows controls. In many cases the wrapper function simplifies your code by converting a SendMessage call into something that looks like a function. For example, the CTreeCtrl::SortChildren() function takes a handle to a tree item and sorts the children of that handle within the control. In your code it might look like this.

m_myTreeCtrl.SortChildren(hMyNode);
That's what you write. Internally the class really sends a message to the tree control. You get to call a nice easy function based interface and MFC takes care of moving the parameters around in the way the message requires. Here's the reformatted function from MFC source code.
_AFXCMN_INLINE BOOL CTreeCtrl::SortChildren(HTREEITEM hItem)
{
    ASSERT(::IsWindow(m_hWnd));
    return (BOOL)::SendMessage(m_hWnd, TVM_SORTCHILDREN, 0, (LPARAM)hItem);
}
The first thing it does is assert that the underlying window handle in your CTreeCtrl object is a valid window! Now I really don't know if bad things will happen to your system if you try and send the TVM_SORTCHILDREN message to a non-existent window. What I do know is that I want to be told if I'm trying to do it! The assert here alerts me immediately if I'm doing something that has no chance of succeeding.

So if you were calling such a function and got an assert error you'd look at the failed line and see that it's asserting that the window handle is an existing window. That's the only thing it's asserting. If it fails the only thing that can be wrong is that the window with that handle doesn't exist. That's your clue to tracking down the bug. From there you would look at the function calling this one and try to determine why the window that you thought existed no longer exists.

So that's a very very brief overview of how MFC uses asserts and how you go about determing why MFC asserted in your project. Now let's look at how we can use assert in our own code.

void CMyClass::MyFunc(LPCTSTR szStringPtr) revisited

Earlier I alluded to a simple check which simply asserts that the pointer passed to a function isn't NULL. We can actually do a good deal better than that. Both MFC and Windows itself provide us with a bunch of functions we can use to determine if a pointer is pointing at valid memory. To refresh your memory here's the original function again, and our first draft at improving it.
void CMyClass::MyFunc(LPCTSTR szStringPtr)
{
    if (*szStringPtr == '7')
        DoSomething();
}
and the first draft of improvements...
void CMyClass::MyFunc(LPCTSTR szStringPtr)
{
    ASSERT(szStringPtr);

    if (*szStringPtr == '7')
        DoSomething();
}
This will alert you to just one kind of error, passing a NULL pointer. It'd be nice if we could also test that the pointer is pointing at valid memory instead of just being a non NULL garbage value. We can do it like this.
void CMyClass::MyFunc(LPCTSTR szStringPtr)
{
    ASSERT(szStringPtr);
    ASSERT(AfxIsValidString(szStringPtr));

    if (*szStringPtr == '7')
        DoSomething();
}
This adds a check that szStringPtr is pointing at valid memory. The test checks that you have read access to the memory and that the memory includes the string terminator. A related function is AfxIsValidAddress that lets you check if you have access to a memory block of a particular size as specified in the call. You can also check if you have read access to the block, or if you have write access to it.

Other kinds of ASSERT checks

In addition to checks that use Windows system calls such as IsWindow() and memory validation checks it's possible to assert that an object passed to a function is of a particular type. If you're writing a program that deals with both CEmployee objects and CProduct objects it's not terribly likely that those objects will be interchangeable. So it makes sense to verify that functions which work on CEmployee objects are passed only those types of objects. In MFC you do it like this.
void CMyClass::AnotherFunc(CEmployee *pObj)
{
    ASSERT(pObj);    //  Our old friend, it can't be a NULL

    ASSERT_KINDOF(CEmployee, pObj);
}
As before, we first assert that the pointer isn't NULL. Then we assert that it's an object pointer of type CEmployee. You can only do this with classes that are derived from CObject and you need to add some runtime support. Fortunately the runtime support is really trivial.

You must have declared the object as being at least dynamic. Let me explain. In MFC you can declare that a class contains runtime class information. You do this by including the DECLARE_DYNAMIC(ClassName) macro in the class declaration and including the IMPLEMENT_DYNAMIC(ClassName, BaseClassName) somewhere in the implementation.

The class definition.

class CMyReallyTrivialClass : public CObject
{
    DECLARE_DYNAMIC(CMyReallyTrivialClass)
public:

    //  Various class members and functions...

};
and the implementation file
IMPLEMENT_DYNAMIC(CMyReallyTrivialClass, CObject);

.
.
.
// Other class functions...

If all you want to do is use the ASSERT_KINDOF macro those two lines are all that's needed. Now, when you write your program, you use the ASSERT_KINDOF macro anywhere that an object pointer is passed to you and it will be tested to see if it does indeed point to an object of that kind. If it doesn't your program crashes in the controlled way mentioned before and up comes the debugger pointing at the failed assertion. From there... well we've already covered that ground.

If your object already contains the DECLARE_DYNCREATE macro or the DECLARE_SERIAL macro you don't need to use DECLARE_DYNAMIC because both those macros include the runtime class information required by ASSERT_KINDOF.

Conclusion

I've tried to illustrate how assert can be used to catch runtime errors and drop you into the debugger at the line of code that caused the assert. We looked at how you can work backwards from the assert to determing the reason the assert failed. Along the way we briefly looked at how you can test in your own software the validity of pointers to memory and how you can check that you were passed a pointer to an object of the kind your code is expecting.

In recent years I've been using (perhaps overusing) asserts as runtime checks in my code. It's become automatic to pepper my code with assertions that pointers are valid and point to the kind of object my code is expecting. I find it pays off. It's a rare occurence these days that I have to cope with a crash caused by a NULL pointer or a pointer to the wrong kind of object.

History

12 Mar, 2004 - Initial version

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here