I stumbled upon an informative article: http://cnicholson.net/2009/02/stupid-c-tricks-adventures-in-assert/ which pointed out a great number of problems that exist in my current suite of debugging macros.
The full code for the final version of the macro is given near the end of the article if you follow the link.
The general form as presented is like this (somebody please correct me if i am wrong in transposing it):
#ifdef DEBUG
#define ASSERT(cond) \
do \
{ \
if (!(cond)) \
{ \
ReportFailure(#cond, __FILE__, __LINE__, 0); \
HALT(); \
} \
} while(0)
#else
#define ASSERT(cond) \
do { (void)sizeof(cond); } while(0)
While thinking about modifying my code with what I have learned, I noticed a couple of interesting variations posted in the comments for that article:
One was that you cannot use this macro with the ternary operator (i.e. cond?ASSERT(x):func()
), and the suggestion was to replace the if()
with the ternary operator and some parentheses as well as the comma operator. Later on another commenter provided this:
#ifdef DEBUG
#define ASSERT(x) ((void)(!(x) && assert_handler(#x, __FILE__, __LINE__) && (HALT(), 1)))
#else
#define ASSERT(x) ((void)sizeof(x))
#endif
I thought the use of logical and &&
is particularly smart in this case and it seems to me that this version is more flexible than one using the if
or even the ternary ?:
. Even nicer is that the return value of assert_handler
can be used to determine if the program should halt. Although I am not sure why it is (HALT(), 1)
instead of just HALT()
.
Are there any particular shortcomings with the second version here that I have overlooked? It does away with the do{ } while(0)
wrapped around the macros but it seems to be unnecessary here because we don't need to deal with if
s.
What do you think?
For the sake of completeness, I published a drop-in 2 files assert macro implementation in C++:
Will prompt you with:
Where
abort()
(on Windows, the system will prompt the user to attach a debugger)abort()
immediatelyYou can find out more about it there:
Hope that helps.
I imagine
HALT
may be a macro (or other stand-in name) forexit
. Say we wanted to useexit(1)
for ourHALT
command.exit
returnsvoid
, which cannot be evaluated as the second argument to&&
. If you use the comma operator, which evaluates its first argument, then evaluates and returns the value of it's second argument, we have an integer (1) to return to&&
, even though we never reach that point becauseHALT()
will cause us to stop long before then.Basically, any function that fills in for
HALT
is probably going to have a return value ofvoid
, because it would make no sense for it to return any value. We could make it return anint
, just for the sake of the macro, but if we're already hacking around with a macro a little more hackery can't hurt, can it?In C and C++ standard library,
assert
is a macro that is required to act as a function. A part of that requirement is that users must be able to use it in expressions. For example, with standardassert
I can dowhich is functionally the same as
Even though the former might not be a very good way to write an expression, the trick is still very useful in many more appropriate cases.
This immediately means that if one wants their own custom
ASSERT
macro to mimic standardassert
behavior and usability, then usingif
ordo { } while (0)
in the definition ofASSERT
is out of question. One is limited to expressions in that case, meaning using either?:
operator or short-circuiting logical operators.Of course, if one doesn't care about making a standard-like custom
ASSERT
, then one can use anything, includingif
. The linked article doesn't even seem to consider this issue, which is rather strange. In my opinion, a function-like assert macro is definitely more useful than a non-function-like one.As for the
(HALT(), 1)
... It is done that way because&&
operator requires a valid argument. The return value ofHALT()
might not represent a valid argument for&&
. It could bevoid
for what I know, which means that a mereHALT()
simply won't compile as an argument of&&
. The(HALT(), 1)
always evaluates to1
and has typeint
, which is always a valid argument for&&
. So,(HALT(), 1)
is always a valid argument for&&
regardless of the type ofHALT()
.Your last comment about
do{ } while(0)
does not seem to make much sense. The point of enclosing a macro intodo{ } while(0)
is to deal with externalif
s, not theif
s inside the macro definition. You always have to deal with externalif
s, since there's always a chance that your macro will be used in an externalif
. In the latter definitiondo{ } while(0)
is not needed because that macro is an expression. And being an expression, it already naturally has no problems with externalif
s. So, there's no need to do anything about them. Moreover, as I said above, enclosing it intodo{ } while(0)
would completely defeat its purpose, turning it into a non-expression.