views:

1675

answers:

6
+2  Q: 

assert and NDEBUG

After reading some threads on misuses of exceptions (basically saying you don't want to unwind the stack if a functions preconditions are incorrect - possibly signalling that all your memory is corrupt or something equally dangerous) I'm thinking about using assert() more often. Previously I have only used assert() as a debug tool and I think this is how a lot of C++ programmers use it. I'm concerned about part of my error handling being turned off by a NDEBUG #define introduced to the runtime builds at some point in the future. Is there a way round this and have others had a problem with this (i.e. should I be worrying about it)?

Thanks, Pat

Edit: The point of the threads I was reading was that if your application is truely buggered then unwinding stack could damage the system, for instance if a destructor wrote something to a file and the file handle was corrupted. I'm not suggesting using assert for normal error handling. The current use case I have is pretty weak but see what you think:


  //check later code won't crash the system
  if( buf.length() % 2 )
    return false;
  // do other stuff that shouldn't affect bufs length
  //copy 2 bytes into buf at a time, if length is odd then don't know 
  //what will happen so use assert to make sure it can't damage anything
  assert( !(buf.length() % 2) );
  for( i = 0; i != buf.length(); i += 2 )
    memcpy( buf + i, data, 2 );
 

edit2: the discussion is here: http://groups.google.com/group/comp.lang.c++.moderated/browse_frm/thread/80083ac31a1188da

+2  A: 

You could build your own assert instead of using the stock C assert.h. Your assert won't be disabled.

Look at how assert() is implemented in /usr/include/assert.h (or wherever). It's simply some preprocessor magic eventually calling an "assert fail" function.

In our embedded environments, we replace assert() all the time.

David Poole
+1  A: 

I would avoid relying on assert for other than a casual way to check assumptions during debugging. In release code, your redefined assert is going to be handled by a random crash, this is not user friendly

If during debugging, an assumption of yours, say a given condition will never be true, turns out to be invalid. Change your error handling code accodingly to take into account that condition.

Otherwise, have a more user-friendly facility for handling assumption violating conditions. Create an exception called CAssumptionViolated, throw it where you would otherwise assert. Catch it in your main routine and provide a means for the user to alert you about the error. Better yet, provide debug information with the exception. Even better, automatically forward the debug information to your organization somehow.

Doug T.
The point of the threads I was reading was that if your application is truely buggered then unwinding stack could damage the system, for instance if a destructor wrote something to a file and the file handle was corrupted.
Patrick
If your "assert" checks for an invalid file handle, when invalid mark the file handle as invalid (perhaps set a flag that tells your code not to use it) then throw an exception. Then your destructor flag can check that flag before writing.
Doug T.
No, the assert and the file handle are separate. If the assert detects that something 'impossible' has happened (as opposed to unexpected) then you could (should?) make an assumption that you can't trust anything, including stack unwinding to some nice logging/ error reporting functionality.
Patrick
A: 

Maybe I haven't understood you correctly, but IMO assert() should not be used in release code to do what you describe i.e. enforce invariants.

If a function's preconditions are incorrect, what would you want to do? How would you want to communicate that to:

  • an interactive user?
  • your support personnel?

In both cases you would need to invoke some error handling code, and (it seems to me) exceptions are just the ticket.

However, if you still want to go this route you could place the following at the top of every source file:

#undef NDEBUG

I do NOT recommend this. Please don't do it. No one will thank you :-)

Seb

Seb Rose
A: 

I've seen programs implement their own assert and verify macros. Where asserts are used in debug mode and verify's in both debug and release mode. Specifically verify's can be used to gracefully exit (or rather more gracefully than outright crashing) from a program when a check fails. Probably one of the first, and best uses that I've seen is in the Unreal code (I believe you can still get the Unreal headers to look at).

Daemin
+2  A: 

Well, a failing assertion is a bug, no more, no less. Same as dereferencing a null pointer, except that you yourself gave your software the stick to hit you. Brave decision, you deserve credit for it!

Jumping out of the problem with an exception hardly helps, it does not fix the bug. So I would recommend implementing your own ASSERT() macro, which:

  • Tries to collect as much data as possible about the failure (assertion expression, stack trace, user's environment, etc)
  • Tries hard to make it as easy as possible for the user to report it to you, and
  • Throws up a message box apologizing for the inconvenience and brutally aborts the app.

If performance is an issue, you may consider having some kind of SOFT_ASSERT() macro that disappears from release builds.

Carl Seleborg
Step 4: Post the data to your server and automatically open a bug.
Frank Krueger
+1  A: 

I like to define my own assertion macros. I make two -- ASSERT tests always (even for optimized builds) and DASSERT only has an effect for debug builds. You probably want to default to ASSERT, but if something is expensive to test, or assertions inside inner loops of performance-sensitive areas can be changed to DASSERT.

Also, remember, that assertions should only be used for totally nonsensical conditions that indicate a logical error in your program and from which you can't recover. It's a test of your programming correctness. Assertions should NEVER be used in place of error handling, exceptions, or robustness, and you should never assert anything related to malformed or incorrect user input -- such things should be handled gracefully. An assertion is just a controlled crash where you have the opportunity to output some extra debugging info.

Here are my macros:

/// ASSERT(condition) checks if the condition is met, and if not, calls
/// ABORT with an error message indicating the module and line where
/// the error occurred.
#ifndef ASSERT
#define ASSERT(x)                                                      \
    if (!(x)) {                                                         \
        char buf[2048];                                                 \
        snprintf (buf, 2048, "Assertion failed in \"%s\", line %d\n"    \
                 "\tProbable bug in software.\n",                       \
                 __FILE__, __LINE__);                                   \
        ABORT (buf);                                                    \
    }                                                                   \
    else   // This 'else' exists to catch the user's following semicolon
#endif


/// DASSERT(condition) is just like ASSERT, except that it only is 
/// functional in DEBUG mode, but does nothing when in a non-DEBUG
/// (optimized, shipping) build.
#ifdef DEBUG
# define DASSERT(x) ASSERT(x)
#else
# define DASSERT(x) /* DASSERT does nothing when not debugging */
#endif
Larry Gritz
thanks for that, some useful code.
Patrick