2

my question might be partially related to When should we use asserts in C? But I'm still wondering which would be a better practice when I have a project with a bunch of assertions spread out at places:

Do I use the DNDEBUG flag to turn asserts into no-ops (as suggested in the related question), or do I surround all the asserts with a if macro like

#ifdef TEST
#include <assert.h>
#endif

....

#ifdef TEST
    assert(...);
#endif

....

and use a -D TEST option at compliation time? Is there some sort of standard or 'convention' on this? I feel like the latter would be neater.. Thanks!

Community
  • 1
  • 1
fy_iceworld
  • 656
  • 2
  • 10
  • 20

2 Answers2

3

Is there some sort of standard or 'convention' on this?

Yes: define NDEBUG in production code. It's the ISO C standard way of doing this and only adds a few milliseconds to your compile cycle (or saves some compared to not defining NDEBUG, as you're including it anyway) because the compiler/preprocessor has to scan through <assert.h> to find out it has to remove things from your code. It doesn't bloat your executable or add any other kind of runtime overhead, unless you do crazy things in a #ifndef NDEBUG block.

Fred Foo
  • 355,277
  • 75
  • 744
  • 836
0

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.)

steveha
  • 74,789
  • 21
  • 92
  • 117