Journal Articles
Browse in : |
All
> Journals
> CVu
> 152
(9)
All > Topics > Programming (877) Any of these categories - All of these categories |
Note: when you create a new publication type, the articles module will automatically use the templates user-display-[publicationtype].xt and user-summary-[publicationtype].xt. If those templates do not exist when you try to preview or display a new article, you'll get this warning :-) Please place your own templates in themes/yourtheme/modules/articles . The templates will get the extension .xt there.
Title: 10 Things You Always Wanted to Know About Assert (But Were Afraid to Ask)
Author: Administrator
Date: 03 April 2003 13:15:56 +01:00 or Thu, 03 April 2003 13:15:56 +01:00
Summary:
This article is aimed primarily at those who are unfamiliar with assertions. However, even advanced programmers may make one or two new discoveries.
Body:
This article is aimed primarily at those who are unfamiliar with assertions. However, even advanced programmers may make one or two new discoveries.
Write
assert(condition);
Some examples:
assert(i < 0); assert(ptr); assert(!str.empty());
If assertions are enabled (see point 8) and the condition is false, the assertion fails, displaying a diagnostic message, then calling abort, a C and C++ standard library function that terminates your program without calling destructors or performing much else in the way of clean-up. Otherwise, if the condition is true, program execution continues as normal.
The exact format of the diagnostic message is implementation defined, but it must include the condition, converted to a string, plus the source file name and line number of the assert call. C99, the latest patchily-adopted standard version of C, additionally requires that the name of the function containing the assert be given.
Ensure your asserted condition have no side-effects: the mere evaluation of the condition should not affect program behaviour. There are a number of reasons for this, separation of concerns being one of them.
assert allows you to document your assumptions about your program's state in code, in such a way that they can be verified automatically at runtime. The dramatic effects of an assertion failure will alert you to any cases where your assumptions do not hold. This makes assert a powerful bug detection tool.
assert is available in both C and C++. In both standard versions of C, C90 and C99, it lives in the C-style header file <assert.h>. In standard C++ it lives in the C++-style header file <cassert> also.
No: assert is a macro so it is not a member of a namespace even when you use the C++-style header file. Alternatively, given the way macros are globally visible, we could say it is a member of all namespaces.
assert is an odd choice of name for a macro, since it is all lower case. This is one instance where you should not follow the language standards' example in your own code: any macros you write should be named using ALL_CAPS.
Almost always not. Most programs should be designed to cope gracefully with I/O errors and the behaviour of a failed assertion is anything but graceful.
Use assert only to verify assumptions that must always be true for the program to function correctly: in other words to detect bugs. The same arguments apply to other runtime checks that should be able to fail gracefully: don't use assert.
No. Memory allocation failure is another example of a failure that should be tolerated gracefully, which makes assert an unsuitable choice. See the previous point.
In C++ when you are using new, this is doubly misguided because new does not return a NULL pointer on allocation failure, it throws a std::bad_alloc exception.
// Please don't do this! foo* ptr = new foo; assert(ptr); // Wrong. Doubly.
Assertions are most useful during program development and testing. Although some people advocate leaving assertions enabled in released code, it is common also to disable them, particularly when the overhead of the checking would affect your program's ability to hit its performance targets. It is not my intention to get into a full discussion as to the pros and cons of disabling assertions in release builds. I believe both approaches to be valid in some circumstances.
Disabling assert calls is supported as standard: assert is defined in such a way that if the macro NDEBUG is defined before inclusion of <assert.h> or <cassert>, assert calls compile to a no-op. In other words, somewhere in these header files lies code equivalent to
#ifdef NDEBUG #define assert(cond) #endif
This provides another good reason why the assert condition should not have any side-effects: if NDEBUG is defined, then the condition will never be evaluated.
Many compilers support debug and release modes of compilation. Often NDEBUG is defined automatically when compiling in release mode. See your compiler's documentation for more information.
Beware: <assert.h> and <cassert> are unlike most header files with respect to multiple inclusion, which may have a different effect from single inclusion. Each time you include one of these files, any previous definition of assert is undefined (using #undef) and assert is redefined. If NDEBUG is defined then the no-op definition of assert is given, otherwise the normal, checking, definition of assert is given. This allows you to write code like this:
// This code is allowed, but not recommended. #include <cassert> void with_assertions_enabled(int i) { assert(i > 0); // Will be checked. ... } #define NDEBUG #include <cassert> void with_assertions_disabled(int i) { assert(i > 0); // Not checked. No-op. ... }
Code like this is confusing so the practice is not recommended. Nonetheless, you may see it in other people's work, so it is good to be aware of it.
Design by contract is a term to describe a strict approach to verifying program assumptions and is most fully documented by Bertrand Meyer, the creator of the language, Eiffel. Briefly, he divides conditions into three categories:
-
Pre-conditions that must be true on function entry.
-
Post-conditions that must be true on function exit.
-
Invariants that must be true both on function entry and on function exit.
The fundamental principle behind design by contract is that a correctly written function promises that its post-conditions and invariants will be true on exit provided that its pre-conditions and invariants are true on entry.
assert provides a mechanism for checking pre-conditions, post-conditions and invariants in C and C++ programs. However, it is fair to say that other languages, particularly Eiffel, more fully support design by contract. For more details of Meyer's thinking see [Meyer].
For checking pre-conditions it is permissible to use an if statement and a throw instead of an assert. For example,
void foo(int i) { if(i < 1) throw std::range_error("foo: i < 1"); }
versus
void foo(int i){ assert(i >= 1); }
There is no hard and fast rule as to which technique is best. I prefer assertions for testing pre-conditions when the calling code will be written by myself or another member of my development team. When the calling code will be written by external developers I prefer to use exceptions here.
Many compilers' assert macros go beyond what is required by the language standards. It is common to be offered the choice to continue execution past an assertion failure (just as if the assertion had not failed) or to drop into a debugger.
Furthermore, developers often create their own assert-like macros which provide more flexibility. Typical extensions are:
-
Extensions similar to those offered by vendors and discussed previously, for working with compilers whose vendors provide only the standard-mandated assertion support.
-
Providing more user-friendly messages. Particularly useful for those whose release builds contain enabled assertions.
-
Multiple levels of protection, instead of the all-or-nothing NDEBUG approach. For example, if you define ASSERT_QUICK (for conditions that are checked quickly) and ASSERT_SLOW (for conditions that take longer to check), you have three speed versus bug detection tradeoffs available: you can build with both enabled, only enabling ASSERT_QUICK or with neither enabled.
-
Assertions that throw on failure (the Eiffel approach). Pre-condition testing aside (see previous point), I don't recommend this approach: assertion failures should be loud and proud, not open to silent catch-and-continue. If you go down this road, think very carefully about the interactions with exception safety.
-
Assertions that automatically log their message to an error file, or to the Windows Event Log. They could even send an e-mail or an SMS message to a system administrator.
Extending assert can be very useful but, to avoid confusion with the standard macro, please use a different name!
We have discussed what assertions are, how they behave, when to use them and, just as importantly, when not to use them. Finally we explored some more advanced issues such as extensions to the basic assert functionality.
You should now know enough to use assertions productively. Even making the basic transition from no assertions to employing the humble standard assert, can, I believe, bring a significant quality boost to the development process.
[Meyer] Bertrand Meyer discusses design by contract: http://archive.eiffel.com/doc/manuals/technology/contract/
Notes:
More fields may be available via dynamicdata ..