Alex Reynolds
Alex Reynolds

Reputation: 97004

Is using assert() for production not favored over if..else.. blocks?

I am finding that using assert(...) makes my code shorter and easier to read, as opposed to lengthy if..else.. blocks. However, are there good technical reasons not to use assert(...) in shipping code, when it does the same thing as testing a return value while using less code?

Upvotes: 7

Views: 4133

Answers (9)

Joseph Quinsey
Joseph Quinsey

Reputation: 9972

Asserts are good. Compile-time asserts are even better. Note:

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

Upvotes: 3

Ben DeMott
Ben DeMott

Reputation: 3624

Having read this article I will share my beliefs about assert:

  1. Yes it's fine to use assert when something absolutely should meet the condition you are asserting.

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

Upvotes: 4

cube
cube

Reputation: 3948

IMO none of the answers here says the most important part: Assert states programmers assumptions. assert(x) says something like "x is always true at this point, you can check that if you want". If the user ever sees assertion error, it means you did something wrong.

What you probably need is a function which would do almost the same thing as assert, but both in release and debug modes. Using check(x) would mean "Check that x is true. If it isn't, tell the user and quit"

Upvotes: 3

jamesdlin
jamesdlin

Reputation: 90184

I think using assert for runtime errors is poor form, particularly because it defies convention:

  • assert typically is compiled out of non-debug builds. Now, you might choose to compile all your builds with the expectation that assert is always enabled, but anyone who inherits your code later might not understand that. It'll be even worse if you write code where your asserts have side-effects (such as assert((fp = fopen(filename, "r")) != NULL)).

  • Similarly, assert is intended to be used for logical errors, not runtime errors. When you start using assert to check runtime errors, you start misrepresenting how your code is supposed to behave to anyone who tries to read it. Maintainers will not be able to distinguish easily between logical errors and potential runtime errors, and the distinction is useful when reasoning about code behavior.

And do you really want your program to terminate abruptly and dump core if a user accidentally leaves some input field blank? That's incredibly user-hostile.

If you feel that using assert is less code, then you could try writing your own, separate macro. That would avoid changing the expected semantics of assert and the hostility. For example, something like:

#define FAIL_IF(cond, action) do { if (cond) { action; } } while (0)

FAIL_IF((fp = fopen(filename, "r")) == NULL, goto exit);

Upvotes: 2

vines
vines

Reputation: 5225

I don't share the point of those who say it's valid. Once I've got screwed by the fact that Qt's asserts were defined like as follows:

#ifdef DEBUG
#  define q_assert(...) ...
#endif

and I used to write something like

q_assert(some_ptr_type a = get_new_ptr());

When I've complied it in release mode, I was quite puzzled by a never initialized.

Upvotes: 1

Amber
Amber

Reputation: 527548

assert() is designed for things that are never supposed to be false in sane circumstances.

if-else is designed for things that may sometimes be false in sane circumstances, because the else portion is designed for handle such situations gracefully.

In short, if statements are designed to avoid crashes; assert() is designed to cause them.

Upvotes: 2

Jerry Coffin
Jerry Coffin

Reputation: 490808

While the other answers have some good information, none seems (to me) to have directly addressed the question you asked.

IMO, yes, there's a fairly significant shortcoming to leaving asserts in (most) production code: you get no control over the error message it displays, which is often rather scary looking.

Instead of something like:

Fatal error: Assertion failed! x != NULL in xxx.c, line 107

I'd rather give the customer something like:

Please contact customer support and give them code xxx:107

Upvotes: 2

user541686
user541686

Reputation: 210781

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.

Upvotes: 4

Joop Eggen
Joop Eggen

Reputation: 109623

Like java Exceptions assert gives a fail-fast. That is a very good practice saving much development time and effort. But the shipping code should have fast releases as a web application, and not something like a CD distro. Furthermore in java exceptions may be intercepted, logged and nicely presented.

Upvotes: 0

Related Questions