Is using assert() for production not favored over if..else.. blocks?
Having read this article I will share my beliefs about assert
:
Yes it's fine to use
assert
when something absolutely should meet the condition you are asserting.Many languages allow you to raise custom errors when asserting, C not having "Exceptions" may produce errors that are a little harder to diagnose without directly looking at the source in question.
If it's a programming error (possibly by the caller), use an assert
.
If it's not a programming error, then use if
/else
and handle the situation appropriately.
Asserts are good. Compile-time asserts are even better. Note:
- BOOST has one,
BOOST_STATIC_ASSERT()
. - C++0x has
static_assert()
. - And GCC since 4.3 has had a built-in
static_assert()
: Does GCC have a built-in compile time assert? - C11 has
static_assert()
(<assert.h>
).
If your environment doesn't already have a static assert, here is a suggestion.
The ASSERT()
macro given below can be placed anywhere in the code, except:
- In a twice-included header file, without a
#ifndef...#endif
wrapper. - In the middle of a structure definition (or enum definition).
- In strict C89 or C90, after a statement. (But you can wrap it in braces!)
If you want stick something in the middle of a structure definition you'll need to use the lengthy, ugly, three-line construct #if...#error...#endif
. And if you do do this, the pre-processor has a much more limited idea of what a "constant expression" is.
This is a refinement of ideas from the web, primarily from http://www.pixelbeat.org/programming/gcc/static_assert.html. This definition is shorter than BOOST_STATIC_ASSERT()
. And, I believe, is better than Linus's suggestion for an improved BUILD_BUG_ON()
. And the do{...}while(0)
wrapper you commonly see is totally inapplicable here, as it limits permissible locations.
This is also simpler than Google's COMPILE_ASSERT/CompileAssert. The 'sizeof bitfield' trick also seems good, from Linux's BUILD_BUG_ON_ZERO()
, but not its useless sibling BUILD_BUG_ON()
.
There are many suggestions for using arrays with a negative index. But with GCC, most of these do not detect a non-constant arg (which is easy enough to do in error), except for the 'extern int foo[expression]', which also gives an 'unused variable' warning. But typedef int array[expression]
seems also to be good: see below.
The definition:
#define CONCAT_TOKENS(a, b) a ## b
#define EXPAND_THEN_CONCAT(a,b) CONCAT_TOKENS(a, b)
#define ASSERT(e) enum{EXPAND_THEN_CONCAT(ASSERT_line_,__LINE__) = 1/!!(e)}
Equally good, I believe, is the following variant, but it is longer by five characters:
#define ASSERT(e) typedef int EXPAND_THEN_CONCAT(ASSERT_line_,__LINE__)[1-2*!(e)]
There is also the do{switch(0){case 0:case(e):;}}while(0)
construct, which I haven't investigated.
Sometimes one needs a variant to handle the case where two different header files happen by chance to have two ASSERT()'s on the same line, or likewise for a source file and a header file. You could handle this via __COUNTER__
, but this isn't supported by some compilers (and is uglier). And we can't use __FILE__
, because it doesn't usually expand to a valid C token (e.g. it has a dot c or dot h). The Mozilla version http://mxr.mozilla.org/mozilla-central/source/mfbt/Assertions.h states that such conflicts "should be rare", but they'll greatly annoy your teammates when it happens. This can also be used to handle several ASSERTS
in a multi-line macro, where __LINE__
doesn't change.
#define ASSERTM(e,m) enum{EXPAND_THEN_CONCAT(m##_ASSERT_line_,__LINE__)=1/!!(e)}
The next variant, ASSERT_zero(),
is similar to BUILD_BUG_ON_ZERO(),
using the 'sizeof bitfield' trick. This yields either:
- a compile error, when
e
is false, or - the value zero.
So it can be used in places where a statement cannot, such as in the middle of an expression.
#ifndef __cplusplus
#define ASSERT_zero(e) (!sizeof(struct{int:!!(e);})) // cf. BUILD_BUG_ON_ZERO(), !C++
#else
#define ASSERT_zero(e) (!sizeof(char[(e) ? 1 : -1])) // careful: g++ has VLAs
#endif