Here is how I do this. Discussion below the code.
In some .h file:
#define PASS ((void)0)
extern void hook(void);
extern void asserthook(void);
#ifdef DEBUG
#define ENABLE_ASSERTS
#endif // DEBUG
#ifdef ENABLE_ASSERTS
#define AssertMesg(expr, mesg) \
do { \
if (!(expr)) \
{ \
asserthook(); \
fprintf(streamErr, \
"%s(%d): assertion failed: ", __FILE__, __LINE__); \
fprintf(streamErr, "%s\n", mesg); \
fflush(streamErr); \
Exit(10); \
} \
} while (0)
#define Assert(expr) AssertMesg(expr, "")
#else // !ENABLE_ASSERTS
#define AssertMesg(expr, mesg) PASS
#define Assert(expr) PASS
#endif // !ENABLE_ASSERTS
And in some .c file:
void hook(void)
{
PASS;
}
void asserthook(void)
{
hook();
}
First of all, my asserts always call asserthook()
which calls hook()
. These functions are just places to set a breakpoint; I also have errhook()
that is called for an error. Usually I just set a breakpoint on hook()
itself, and then anytime my code is taken down by an assert, I have the debugger stopped right on the error with the stack backtrace in just the spot I need.
When you are trying to make a multiline macro that will act like a C statement, the usual way to do it is to put it in curly braces and then wrap those braces in do
/ while (0)
. That's a loop that executes a single time, so it's really not a loop. But wrapping it like that means it is a statement and when you put a semi-colon on the line to terminate the statement, it's actually correct. Thus code like this will compile without errors and do the right thing:
if (error)
AssertMesg(0, "we have an error here");
else
printf("We don't have an error after all.\n");
If you didn't do the silly do
/ while (0)
wrapper, and just had the curly braces, the above code won't work! First, the compiler would wonder why you have a ;
right after a curly brace and before an else
; second, the else
would associate with the hidden if
inside the AssertMesg()
macro, and the printf()
would never be called. You could fix the latter problem with explicit curly braces but it is clearly better to set up your macro so that it works in all situations, and that is what the do
/ while (0)
does for you.
(void)0
is my preferred do-nothing statement. You could just use do {} while (0)
if you prefer, or anything else that has no side-effects and doesn't use any variables.
The worst thing about the do
/ while (0)
trick is that you sometimes see error messages that are complaining about a do
loop, and since it's hidden inside macros you need to remember what is really going on. But it's the best way I know to make multiline macros work correctly.
When possible, you should use static inline functions rather than macros. But macros are completely portable to even sucky lame C compilers, and you need to use a macro for an assert so you can have __FILE__
and __LINE__
expanded properly.
(You could write a varargs function that does the actual assert, and then just make a macro that expands to a single call to that function, as long as varargs work correctly on all the compilers you use. I think I could do that now, but I already have the multiline macro solution working and I haven't touched it in a long time.)