I've written a (rather long) article on error handling on general,
particularly
on assertions, exceptions and return values. It is hosted on my
personal blog
on blogspot, not because that blog is interesting, but because I have
nowhere
else to post it.
http://mcdougalljonathan.blogspot.com/2011/03/on-matter-of-exceptions.html
The four sections of the article are as follows:
. Exceptions vs. return values
. Throwing objects vs. throwing fundamental types
. Exception hierarchies vs. embedding values
. Lack of a finally construct
and here are the conclusions of the article:
"Assertions, return values and exceptions provide a way of signaling
logic
errors (coding mistakes), runtime errors (part of the normal execution
flow) and
exceptional cases (that break the normal execution flow). Using them
together
allows the normal execution path to be free of error-checking while
still being
able to distinguish between the different outcomes of an operation.
Exceptional
cases are propagated until a function knows how to deal with them.
Creating a hierarchy of exceptions does not solve any real problem:
catching the
base class does not give enough information about the error, while
catching the
derived classes eliminate the need for a base class. Having a single
base class
for all exceptions is also irrelevant: a catch(...) is better to
swallow any
exception and expecting a member function to return meaningful
information is
invalid in most cases. The point where the exception was thrown might
not know
enough about the context. Embedding values such as an integer to
distinguish
between different types of errors is invalid for the same reason.
Embedding a memory-allocating string is dangerous because it might
throw an
exception of its own. Static strings might work but are difficult to
internationalize.
The finally construct is usually better implemented with a simple RAII
class or
a class that can remember and reset a current state if this state is
too
complex."
I'll appreciate any kind of comment, rant or praise.
--
Jonathan Mcdougall
--
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
I disgree with the way your example checks for logic errors. You use
the C assert macro. I would throw a std::logic_error for a logic
error.
If it was for a project where I owned 100% of the code I would create
a class that inherits from std::logic_error. Let's call it
MyLogicError. This enables me to put a breakpoint in the ctor for
MyLogicError, as an easy way to catch all logic errors as soon as they
are thrown.
I have several issues with using the C assert macro for asserting in C+
+ code:
1) It is a no-op in production. I want logic errors to be detected in
production as well as in development. I realise this is a contentious
statement. There are many who say that production code should NOT do
this due to the expense. I say that the expense is often not as great
as is feared and what little expense there is, it is a price worth
paying. We may have to agree to disagree on this one.
2) When assert does trigger it writes to stdout (or is it stderr). IMO
library code should not do this. The caller, or someone else further
up the call chain can decide whether to write to stdout/stderr, but
not a library. The way to give responsibility to code further up the
chain is to throw an exception.
3) When assert triggers, after writing to stdout/stderr it core dumps.
Again, I think library code should not do this. The usual argument is
that it is useful to have a core for debugging. I agree that this is
useful for UNIX environments. It is not so useful on Windoze, where
the expectation seems to be to use the Visual Studio debugger and run
the program again, catching the assertion as it occurs.
Regards,
Andrew Marlow.
I, too, value greatly code where error checking is out of the "happy"
path. (Therefore, I like exceptions ;-)).
> Creating a hierarchy of exceptions does not solve any real problem:
> catching the
> base class does not give enough information about the error, while
> catching the
> derived classes eliminate the need for a base class.
I disagree with this very much. Here's how I see things: base class
defines most basic interface to report the error (hence what() of
std::exception). Derived classes define additional data explaining the
error, and that might be used in particular circumstances (e.g. if
code throws network_error, and error_code is X, a retry might be
feasible; alternatively, one could derive a retriable_error from
network_error specifically).
> Having a single
> base class
> for all exceptions is also irrelevant: a catch(...) is better to
> swallow any
> exception
I disagree with this, too. Situations where I need to swallow any
exception can be counted on fingers of one hand. Often, the purpose is
to cleanup something and rethrow from catch(...), but then RAII is
better and if that is not easily accessible, I use ScopeGuard. At the
very least, we are talking about a last-ditch catch, and even this can
benefit from catching _some_ exception type, even if most base one,
and reporting error using error info provided by it. So IMO single
base class is rather important.
> and expecting a member function to return meaningful
> information is
> invalid in most cases. The point where the exception was thrown might
> not know
> enough about the context. Embedding values such as an integer to
> distinguish
> between different types of errors is invalid for the same reason.
With this, I agree. The only solution to this is to, at "strategically
chosen points", catch, add context, and rethrow. boost::exception is a
good example. Alternatively, an "inner" exception approach à la Java
et. al is useful (that needs some work with C++ though).
By the way, in order to add this additional context, having a single
base class helps, too (otherwise, one ends up with several catch()
clauses in "add context" points, which is ugly).
> Embedding a memory-allocating string is dangerous because it might
> throw an
> exception of its own.
Embedding it in an exception, right? It is dangerous, but better than
not doing it IMO. If code catches exceptions by a const reference (not
doing it is an error IMO), there is no danger.
> Static strings might work but are difficult to
> internationalize.
In fact, I don't believe that they works at all, i18n or not. They
don't work because IMO the exception text needs to change depending on
the context. E.g. an exception was thrown when opening a file; if
exception object doesn't contain file name, and doesn't have it in
e.g. what(), it is IMO a bad error info. And it should be noted that,
for this particular example, at the place error happened, all context
needed to create a nice exception object is present. That is IMO
inadmissible. Never, ever lose throw without best possible context
info, guys, please! ;-)
> The finally construct is usually better implemented with a simple RAII
> class or
> a class that can remember and reset a current state if this state is
> too
> complex."
Amazingly ;-), finally is one construct C++ doesn't have and many
other mainstream languages do, and that's IMO A Good Thing©.
Otherwise, I have a complaint about one thing in your text: there is a
rather poor presumption that error info coming from database
operations is limited to "bad query" and "no database" (I know, it's
for the sake of example, but that detail drags the example down). In
reality, failure modes (as seen by error-return values or whatever)
are much, MUCH more numerous than that. Handling that is very hard,
and this is where, IMO, error-return falls apart, and fast.
What happens, in practice, is that we either
* take a fair amount of shortcuts to reduce error surface, and
consequently, we lose quality error info,
either
* we have an inordinate amount of work on our hands.
IMO exceptions help there, a great bit.
Goran.
I disagree with your disagreement. (-:
> If it was for a project where I owned 100% of the code I would create
> a class that inherits from std::logic_error. Let's call it
> MyLogicError. This enables me to put a breakpoint in the ctor for
> MyLogicError, as an easy way to catch all logic errors as soon as they
> are thrown.
Which is not very useful in a production environment, or in unit
testing. You don't run these in a debugger. Instead, you let the code
crash and use a debugger to inspect the core file.
> I have several issues with using the C assert macro for asserting in C+
> + code:
>
> 1) It is a no-op in production. I want logic errors to be detected in
> production as well as in development. I realise this is a contentious
> statement. There are many who say that production code should NOT do
> this due to the expense. I say that the expense is often not as great
> as is feared and what little expense there is, it is a price worth
> paying. We may have to agree to disagree on this one.
Whether assert is or is not working in production code is rather a
matter of the configuration. Whether it prints out data is really mostly
irrelevant. What is important that it leaves a core file behind which
can be inspected by a post-mortem debugger. If an exception is thrown,
parts of the call context is lost, and why do you want to catch logic
errors in first place? If the program is in an inconsistent state, it is
in an inconsistent state, and nothing will repair it. If you could
repair it, it is not a logic error in first place.
> 2) When assert does trigger it writes to stdout (or is it stderr). IMO
> library code should not do this. The caller, or someone else further
> up the call chain can decide whether to write to stdout/stderr, but
> not a library. The way to give responsibility to code further up the
> chain is to throw an exception.
Just a matter of configuration. And, besides, the stderr output is
typically irrelevant anyhow.
> 3) When assert triggers, after writing to stdout/stderr it core dumps.
This is a good thing.
> Again, I think library code should not do this.
Yes, it should, very much so!
> The usual argument is
> that it is useful to have a core for debugging. I agree that this is
> useful for UNIX environments. It is not so useful on Windoze, where
> the expectation seems to be to use the Visual Studio debugger and run
> the program again, catching the assertion as it occurs.
I guess this is more an argument against Windows than an argument
against asserts, then. Core dumps are extremely useful for analyzing
problems.
Greetings,
Thomas
Of course, nowadays it ain't now problem at all to create useful dump
files on Windows - via assert() or otherwise). We do it all the time.
Windows even has the built-in option to send the crash dump of a crashed
application to it's developers (that's what WER is for).
It's just that this ain't taught in any normal (Windows) C++ tutorials,
which is probably a good thing :-)
cheers,
Martin
I think you have overlooked one practical use for a hierarchy of
exceptions: diagnostics. Any descriptive message does not have to be be
present in the exception object, it can be constructed by the class
what() method (assuming the exception is derived from std::exception).
Whether or not the specific nature of the exception determines the
action on catch is distinct from how the error is reported.
--
Ian Collins
I won't write that I disagree with your disagreement to disagree. ;)
As a matter of fact I would combine the two; assert and
std::logic_error. I would create my own myAssert that could be
configured to dump or abort() during development and throw
std::logic_error in production code.
When developing, one can easier use a debugger if myAssert does an
abort() or equivalent. In production a std::logic_error is much better
since it could provide better logging and give a graceful and consistent
shutdown to the user.
I could stretch myself to allow core dumps at close beta-test sites, but
is there any benefit at all doing a core dump in production?
/Daniel
In this case, you end up with a non-retriable network_error and a
retriable_error that derives from network_error. Code that deals with
network_error does not have enough information to figure out whether
the exception is retriable or not. In this case, I'd have two types
nonretriable_network_error and retriable_network_error without a base
class. I don't see the need for a hierarchy here.
> > Having a single base class for all exceptions is also irrelevant
>
> I disagree with this, too. Situations where I need to swallow any
> exception can be counted on fingers of one hand.
Definitely: functions that are called from a context in which exceptions
must not be leaked, such as destructors or thread functions.
> At the very least, we are talking about a last-ditch catch, and even
> this can benefit from catching _some_ exception type, even if most
> base one, and reporting error using error info provided by it.
Like I wrote in the article, what kind of error reporting? If it's to
the user, this is rife with problems: insufficient information about
the context, internationalization and the fact that this thread might
not have access to the user interface.
If it's for debugging, I prefer logging the problem at the source
instead of throwing it.
And catching a base class doesn't solve the problem of potentially
having to deal with more than one hierarchy, which is a common problem
when dealing with multiple libraries.
> > The point where the exception was thrown might not know enough about
> > the context.
>
> With this, I agree. The only solution to this is to, at "strategically
> chosen points", catch, add context, and rethrow. boost::exception is a
> good example. Alternatively, an "inner" exception approach a la Java
> et. al is useful (that needs some work with C++ though).
But again, to what end? If the behavior in response to an exception is
clear (a network_exception needs to reconnect, a file_not_found needs to
alert the user), it does not need additional context. Additionally,
I don't think you're suggesting using that kind of stack trace facility
in production to choose the right behavior (if the third layer was in
frob_widget(), call unfrob_bar()).
Therefore, is this stack trace used only for debugging/logging? Then
perhaps. But this looks like a lot of work that could be solved with
either an assert or appropriate logging. Still, if the application
cannot break into a debugger, I agree that this is a good alternative
or complement to logging ("entering this", "exiting that", "problem
here").
> > Embedding a memory-allocating string is dangerous because it might
> > throw an exception of its own.
>
> Embedding it in an exception, right?
Yes.
> It is dangerous, but better than not doing it IMO. If code catches
> exceptions by a const reference (not doing it is an error IMO), there
> is no danger.
>From what I understood by read 15.1 in C++03, there is always at least
one copy of an exception, regardless of the catch handlers. I'm putting
together 15.1(3), (4) and (5):
. A throw-expression initializes a temporary object, called the
exception object
. The memory for the temporary copy of the exception being thrown is
allocated in an unspecified way
. If the use of the temporary object can be eliminated without changing
the meaning of the program [...], then the exception in the handler
can be initialized directly with the argument of the throw expression.
The last point especially tells me that without optimization, a
temporary is created from the throw operand using the copy-constructor,
which is then used to initialize the exception object in the handler.
Therefore, I think an implementation is free to use the copy-constructor
for every exception thrown.
> > Static strings might work but are difficult to internationalize.
>
> if exception object doesn't contain file name, and doesn't have it in
> e.g. what(), it is IMO a bad error info
We agree on that. "Might work" meant that if, in a particular program,
no error message needs more information from the surrounding context,
static strings can be used, "but are difficult to internationalize."
> finally is one construct C++ doesn't have and many other mainstream
> languages do, and that's IMO A Good Thing
We agree again. Finally leads to poor coding practices.
> Otherwise, I have a complaint about one thing in your text: there is a
> rather poor presumption that error info coming from database
> operations is limited to "bad query" and "no database"
That wasn't my intention. I am fully aware of the complexity of error
handling. I wanted a context most people are familiar with (database,
statements, queries, items, ids) and that could illustrate the three
types of errors (logic error: malformed query, runtime error: item not
found, exceptional case: database disconnected).
However, I'll add something that explains that simplification.
> What happens, in practice, is that we either
> * take a fair amount of shortcuts to reduce error surface, and
> consequently, we lose quality error info,
> either
> * we have an inordinate amount of work on our hands.
Amen to that. But it doesn't mean that paying attention to your error
handling isn't important, nor that discussing it is irrelevant. I've
seen people assert for files not found or throw exceptions for a coding
mistake. I was trying in this article to explain the difference between
different error reporting facilities and ways to use them efficiently.
--
Jonathan Mcdougall
I'm not sure whether we agree on the definition of a logic error. I'm
talking about coding errors. In my example, a malformed query, such as
"selllect * from mytable"
is what I call a "logic error". When such an error is detected, I am
of the opinion that the program needs to crash immediately.
> If it was for a project where I owned 100% of the code I would create
> a class that inherits from std::logic_error. Let's call it
> MyLogicError. This enables me to put a breakpoint in the ctor for
> MyLogicError, as an easy way to catch all logic errors as soon as they
> are thrown.
Sure. In fact, I usually have a templated my_assert() in my projects
that I can control better than the standard assert macro. Using assert()
in a Windows application for example is sometimes frustrating since it
pops up an alert window with the information. That window is still
running the message pump, which may then give you a stack overflow if
the problem is in a handler. Calling DebugBreak() makes things a lot
easier.
The point was not whether assert() is a good way to assert, but rather
whether asserting is the right choice in some cases.
However, there are two problems with throwing a MyLogicError:
1) The calling code cannot do anything with it. If the query was badly
written, you'd need self-modifying code to fix it.
2) You have two *very* different paths while debugging and in
production. I cordially dislike conditional compilation (except for
include guards and specific situations in a porting layer) but I
don't mind having verification code in an assert() disappear in
production.
However, in production, your code doesn't disappear, it now throws
an exception. This needs a lot more work to test.
> When assert does trigger it writes to stdout (or is it stderr). IMO
> library code should not do this.
> [...]
> When assert triggers, after writing to stdout/stderr it core dumps.
Not all implementations behave that way. Some break in a debugger if it
is present.
But again, I'm not sure we agree on logic errors. If I pass a nullptr to
a library that doesn't expect it, I want it to crash right there and
right now so I can know what happens.
--
Jonathan Mcdougall
Maybe it does just boil down to how useful it is to have a core dump.
Part of the answer to that question seems to be 'it is not very useful
to Windoze programmers but UNIX programmers often find it useful'.
Maybe the fact that some programmers find it very useful and others
prefer an exception which they may want to handle (see below) means
that it should be configurable. So rather than throw, maybe a project
needs some centrally defined class or function that provides a
'assert_logic_error' method. The trouble with this is that it does
have to be centrally defined. That approach doesn't work well with
libraries that come from other groups in the organisation. These won't
be using assert_logic_error so the behaviour of the app in the face of
logic errors may not be consistent. The nice thing about throwing
std::logic_error is that it is much easier to persaude other groups to
adopt at as the policy for detecting logic errors. Ho hum.
I can think of at least two classes of app I have worked on recently
where we do want to trap and handle logic errors. It is not enough to
let the code blunder on assumming it will core dump, saying "it serves
the programmer right". Both apps had a large number of time consuming
jobs to run, serially, and when one fails for WHATEVER reason the app
needs to log the error and continue. Each job is isolated enough that
an error in one job, even if it is a logic error, is not likely to
adversly affect subsequent jobs. But we may have to leave that
discussion for another thread.....
Regards,
Andrew Marlow
In unit testing, it is more common and useful to catch and report
exceptions rather than abort. I often map assertions to exceptions when
building for unit tests to verify the assertions fire when expected.
I do agree than an assert is more useful than an unhandled exception in
production code.
--
Ian Collins
Actually, we do :-)
> I'm talking about coding errors. In my example
[good example snipped]
So am I.
> When such an error is detected, I am
> of the opinion that the program needs to crash immediately.
For some programs this is definitely the right thing to do, but not
for others. It depends on the program. The program I have in mind is
performing a large risk analysis on a book with hundreds of complex
multi-legged deals. The price of each of these is VERY expensive to
compute and each is a relatively isolated computation. A job that
computes the risk for the whole book needs to skip deals that won't
price due to coding errors like the one you gave. Yes, I know it's a
coding error, but these things happen in very large systems.
> The point was not whether assert() is a good way to assert, but rather
> whether asserting is the right choice in some cases.
Yes.
> However, there are two problems with throwing a MyLogicError:
>
> 1) The calling code cannot do anything with it. If the query was badly
> written, you'd need self-modifying code to fix it.
I disagree. My program *can* do something with it. In my example the
risk analysis skips that deal and goes on to the next one.
> 2) You have two *very* different paths while debugging and in
> production.
Not with my approach you don't. I would throw a std::logic_error in
both cases.
> I cordially dislike conditional compilation
So do I.
> But again, I'm not sure we agree on logic errors. If I pass a nullptr to
> a library that doesn't expect it, I want it to crash right there and
> right now so I can know what happens.
I don't. I want my program to skip to the next deal (and reporting the
deal failure as a logic error of course).
I think the point is that there are cases (like mine) where you want
to be able to trap and handle the error and there are cases where you
want the program to blow up with a core dump. I am coming to the
conclusion that the behaviour needs to be configurable. IMO the place
to define this configuration in that part of the code that owns
"main". Libraries that may be used by both kinds of program need to
provide this configuration ability and invoke it from "main". Then we
will *both* be happy. The trouble is, there is no general purpose way
to do this so each library will do it differently. This is why, in the
meantime, I prefer the throwing approach. Programs like yours that
don't attempt to catch and handle logic errors will still just blow up
because the exception will bubble up to main.
The sub-optimal bit with my approach is you won't get the core dump
you want and the unwound stack means you don't get much info on where
it went wrong (just the filename and line number if you put them in
the string you gave to std::logic_error). This is why I would like to
see an official C++ standard way to get a stack trace. If such a
mechanism existed it could be used in MyLogicError which you could
trap and report in main. Until then, MyLogicError could call abort in
the ctor (yes, I know that's ugly. Sigh).
Regards,
Andrew Marlow
--
No, it is a no-op if you define NDEBUG. If you do that or not is up to you.
> 2) When assert does trigger it writes to stdout (or is it stderr). IMO
> library code should not do this. The caller, or someone else further
> up the call chain can decide whether to write to stdout/stderr, but
> not a library. The way to give responsibility to code further up the
> chain is to throw an exception.
Well, there are cases where you can detect internal inconsistency, like e.g.
in a linked list when node->next->prev != node. If you reach that place,
there is little left you can do, because you can't determine which of the
two pointers is wrong. If you reach such a state, continuing program
execution is IMHO not an option. Since you terminate anyway, writing to
stdout also doesn't hurt.
BTW: This verification that e.g. all elements of a linked list are properly
connected is something you can assert. I wouldn't want to do that in a
release build though, therefore I define NDEBUG there. YMMV.
Uli
--
Domino Laser GmbH
Geschäftsführer: Thorsten Föcking, Amtsgericht Hamburg HR B62 932
See my other reply to Thomas. Windows supports core dumps and they are as useful there as they are - I guess - on *nix.
I can't think of why you would believe that programmers working with Windows wouldn't find them useful.
cheers,
Martin
Yes, of course, but you are missing one important point: retriable
error is tied to code that specifically looks for it (code that might
want to retry). The purpose is __not__ to catch it everywhere and
retry.
But all that is tangential. Without a base class, One important point
remains: any last-ditch catch results in "it didn't work", which is
worse than e.g. "it didn't work " + e.g. e.what().
>> At the very least, we are talking about a last-ditch catch, and even
>> this can benefit from catching _some_ exception type, even if most
>> base one, and reporting error using error info provided by it.
>
> Like I wrote in the article, what kind of error reporting? If it's to
> the user, this is rife with problems: insufficient information about
> the context, internationalization and the fact that this thread might
> not have access to the user interface.
>
> If it's for debugging, I prefer logging the problem at the source
> instead of throwing it.
Here, you presume that at the source you have enough context info,
which is not the case in general. At best, your log is going to be:
<something failed>
from some catch: something else failed
from some other catch: something other else failed
Whereas, if you throw and add context, and log at end, you get more
natural log order: highest-level op failed when doing high-level op
when doing low-level op.
> And catching a base class doesn't solve the problem of potentially
> having to deal with more than one hierarchy, which is a common problem
> when dealing with multiple libraries.
+1. I blame libraries for that. I adore those who allow me to plug in
my own exception type.
Goran.
--
In user-facing applications, unhandled exceptions are *always* wrong.
No user on the face of this earth has ever said "Thank God it
crashed! I didn't want to save my work anyway." The best thing that
can be done is to tell the user what happened and give them the option
to (try to) exit gracefully. If they continue on, they assume the
risk involved.
Mike
--
I wonder if it's on-topic but I want to share some low-level
implementation issues - I see that the main issue is how to declare
underlying logic of possible exception-generating code and apart of
that how to alloc and propagate context via execution stack.
a) Registration: at first we register own exception types with
priorities
b) Allocation: instead of on-the-fly allocation of context you have to
allocate exception area (fixed-sized frames) before main flow
c) Propagation: we need own versions of assert, try and catch
communicating with stack
d) Exception handling: for each assertion and try we need to
_explicitly distinguish_ in code between condition and error handler,
because then you can easily predefine underlying logic for crucial
fragments of code
Pseudocode:
#define propagate()
{
free_exception_frame(...); // top of stack
frame = get_exception_frame();
if( ! frame) return;
call(frame.handler());
}
// assertion handler is differentiated from function body for _each_
function separately and must contain propagate()
inline my_fun_assertion_handler_1() { printf("oops, exception in
my_fun()\n"); propagate(); };
// #define assert(cond, recovery, handler)
#define assert(cond, handler)
{
push_exception_frame(type, pri, handler); // , fixer); ?
cond_result = eval(cond);
if(cond_result)
{
// recovery() -> maybe attempt to fix situation here if you know
_logic_ of current problem ?
// I mean that we can propagate logic instead of exception type:
chain of fixers instead of chain of contexts
handler(); // contains propagate
}
pop_exception_frame(...);
return 0;
}
// assert(a > 15, f() { a = 15; global_var_logical_a_error = true; },
my_fun_assertion_handler_1() );
#define try_catch(code, catch, type)
{
code(); // body of try
we_have_ex = check_exception_stack;
if(we_have_ex)
{
pop_exception_frame(type, pri, handler);
if(type.pri = pri)
{
// recovery()
catch(); // body of catch
// finalization here ?
propagate();
}
}
}
#define throw(e)
{
push_exception_frame(e, handler) // handler may be null
propagate();
}
// we can observe that throw() is ~equal to assert(true,
{ push_exception; propagate; } )
etc.
Conclusion:
a) predefined recoveries (func pointers) instead of dynamic contexts
(variable sized) with fixed exception stack frames.
b) if you can write minimal recovery() then you have implicitly coded
logic of exception ...
if you cannot it's "hard" exception, I can say, and we dump core.
What do you think ?
Regards,
Tomasz Budzeń
--
Just to make sure I understand: some network errors are retriable,
others are not. Some parts of the code will want to retry if possible,
but others will never retry even if possible.
This sounds convoluted to me, but I do agree that a hierarchy works
better here.
> But all that is tangential. Without a base class, One important point
> remains: any last-ditch catch results in "it didn't work", which is
> worse than e.g. "it didn't work " + e.g. e.what().
See below.
>>> At the very least, we are talking about a last-ditch catch, and
>>> even this can benefit from catching _some_ exception type, even
>>> if most base one, and reporting error using error info provided
>>> by it.
>>
>> If it's for debugging, I prefer logging the problem at the source
>> instead of throwing it.
>
> Here, you presume that at the source you have enough context info,
> which is not the case in general.
I'm not sure I understand. You just said that throwing an exception
with
a string that can be retrieved through a base class is better than "it
didn't work", but now you're saying that the source might not have
enough context.
I think that whether the source has enough context or not is
irrelevant
with logging since the path that led to the problem should have been
logged. This is something that is not possible with exceptions, unless
some kind of stack trace is available.
> Whereas, if you throw and add context, and log at end, you get more
> natural log order: highest-level op failed when doing high-level op
> when doing low-level op.
I did not take into account having some kind of stack trace in an
exception. This effectively eliminates the problem of context. If this
is used for debugging (and therefore does not need localization) and
is
guaranteed not to throw, then it is an interesting alternative to
logging.
--
Jonathan Mcdougall
An unhandled exception and an assert are pretty much the same thing.
They crash. The point was not whether one is better than the other, it
was whether asserting is better than throwing (and handling) a
logic_error.
> The best thing that can be done is to tell the user what happened and
> give them the option to (try to) exit gracefully.
Ugh, this is assuming that the user reads the message, understands it
and takes an informed decision. I would rely on very few users for
this
kind of decision.
> If they continue on, they assume the risk involved.
Well, that's the problem. When you get to a point where you need to
assert or throw a logic_error, what can you do? Do you take the chance
of saving the work, possibly corrupting a file or a database? Your
program is an inconsistent state, all bets are off.
Personally, I want to crash right there to make sure I don't format
the
drive or send dirty pictures to someone. Then I want to get the core
dump and see what happened so I can fix it.
--
Jonathan Mcdougall
Then I wouldn't consider this kind of problem a logic error. I would
call it an exceptional error. I do not understand the program you
describe, but it looks to me as if the "coding errors" you mention are
either a third-party (perhaps provided by a user?) or the result of
bad
data. These are not logic errors.
However, if what you describe are errors made by programmers working
on
the project, then you need more test units :)
> > However, there are two problems with throwing a MyLogicError:
>
> > 1) The calling code cannot do anything with it. If the query was
> > badly written, you'd need self-modifying code to fix it.
>
> I disagree. My program *can* do something with it. In my example the
> risk analysis skips that deal and goes on to the next one.
Your program seems to be processing independent jobs that can be
cancelled and restarted at will in case of errors. I did not have this
case in mind when I wrote the article.
A logic error implies an inconsistent or unpredictable state. It is a
post-condition that is not respected. It makes any attempt to run the
program further dangerous and/or futile (that bad query will always be
bad.)
If you can isolate the faulty module from the system, then I would not
consider the problems you describe as logic errors from the point of
view of the system. I would consider them exceptional errors that need
to be handled.
However, from the point of view of an individual job, I would still
consider a coding mistake as a logic error. In this case, "assert"
becomes a generic term meaning "abort right now and provide some means
of postmortem debugging, while allowing the system to continue by
returning a specific value."
Finally, like I mentioned in the end, these were my opinions on
general cases. There are of course situations where they don't stick.
Yours might be one of them.
> > 2) You have two *very* different paths while debugging and in
> > production.
>
> Not with my approach you don't. I would throw a std::logic_error in
> both cases.
You were talking about setting a breakpoint in the ctor during
debugging. I interpreted that as breaking into a debugger to inspect
the
problem. If you are saying that after the inspection you let it run to
make sure the rest of the program behaves correctly, then I stand
corrected.
Still, spending time in a debugger might modify timing and introduce
additional problems. YMMV.
> > But again, I'm not sure we agree on logic errors. If I pass a
> > nullptr to a library that doesn't expect it, I want it to crash
> > right there and right now so I can know what happens.
>
> I don't. I want my program to skip to the next deal (and reporting the
> deal failure as a logic error of course).
Again, I would then not consider this a logic error, but an
exceptional
error.
--
Jonathan Mcdougall
Like I wrote elsethread, having two radically different behaviors
during
development and production does not strike me as being a particularly
good idea.
> When developing, one can easier use a debugger if myAssert does an
> abort() or equivalent. In production a std::logic_error is much better
> since it could provide better logging and give a graceful and
> consistent shutdown to the user.
I don't agree that throwing an exception provides better logging (but
that is one of the points of this thread) and except for a "sorry, the
program crashed" message, I don't see how handling a std::logic_error
is any better than asserting.
> I could stretch myself to allow core dumps at close beta-test sites,
> but is there any benefit at all doing a core dump in production?
Using the core dump to fix the bug?
--
Jonathan Mcdougall
The class looks something like this:
class Exception
{
std::string Message; // free form text often filled in by
strerror() or the equivalent
std::string Location; // file:line
std::string StackTrace; // see gcc header execinfo.h, function in
backtrace()
int SystemError; // the Operating system error code if one
exists (example 2 file not found)
int ApplicationError; // an error code more apropos to the
application itself (example 27 invalid username).
// member functions left as an exercise for the reader.
};
The thrower should not be concerned about how the exception is
handled. All he should do is throw as much information as possible and
that Exception class gives you just about all there is to know. It is
not the job of the thrower to be concerned with how the exception is
handled. I find exception hierarchies unnecessary and productive of
kludge.
I mostly catch like this:
catch(const Exception& e)
{
}
catch(const std::exception& e)
{
// Only because other libraries I might be using subclass from
std::exception
}
Catching (...) should only be done in production code as a last resort
and never during development as it quashes errors. One of the major
reasons to use exceptions is to make sure errors get handled. Having
said that, never say never.
Programmers have all sorts of standards they use to define WHEN to
throw an exception. I use one simple rule: If the function cannot
fulfill its purpose, throw an exception. That what exception means:
outside of the domain I am designed to handle.
A common example is a FileOpen function, like this:
int FileOpen(const char* name, int flags)
{
int rval = ::open(name, flags);
if (rval == -1)
{
/*
BIG ARGUMENT HERE. Return rval? return 0? throw exception?
The caller of this function is depending on getting an open
file. That's why he called it. If he doesn't get an open
file we can assume he's screwed too and cannot continue with
his task. I say the proper thing to do is
throw an exception. If you follow this philosophy your code
becomes at least 5x less complex.
*/
}
return rval;
JIT (Just In Time) Debugging.
When the program crashes you get a dialog where you can tell Bill Gates
about it
or invoke the debugger, or just accept the crash. When the debugger is
invoked
the process state is intact. And when everything works as well as it should,
it's as if you had run the program to this point in the debugger.
Of course that's not very useful for a client site crash, but at least for
some
it's very convenient for the development cycle.
Cheers & hth.,
- Alf (with fond memories of Dr. Watson who, apparently, is still lurking
there)
--
blog at <url: http://alfps.wordpress.com>
I also like consistency. I know you disagree but I would rather see an
problem throwing an exception in both development and production. Then,
if I find a bug that is reproducable I would switch to assert and create
core dumps that I can analyze.
>> When developing, one can easier use a debugger if myAssert does an
>> abort() or equivalent. In production a std::logic_error is much better
>> since it could provide better logging and give a graceful and
>> consistent shutdown to the user.
>
> I don't agree that throwing an exception provides better logging (but
> that is one of the points of this thread) and except for a "sorry, the
> program crashed" message, I don't see how handling a std::logic_error
> is any better than asserting.
>
First I see it as a user experience. It might sound stupid, but I prefer
a "sorry, the program crashed". If it also can help me save important
files before crashing, then I'm even happier.
I see another benefit as well. If a plug-in throws instead of core
dumps, the main program may choose to unload the plug-in instead of
piping the user's work to /dev/null making him/her go take a coffe.
>> I could stretch myself to allow core dumps at close beta-test sites,
>> but is there any benefit at all doing a core dump in production?
>
> Using the core dump to fix the bug?
>
That's a dream scenario. But it demands quite a lot from your logistics
to get hold of a user's core dump. If you manage to do that in an easy
way, that's very good.
That brings me to a question:
Is there a way to create a core dump from where you are and then
continue execute the code?
[...]
> > When such an error is detected, I am
> > of the opinion that the program needs to crash immediately.
> For some programs this is definitely the right thing to do, but not
> for others. It depends on the program. The program I have in mind is
> performing a large risk analysis on a book with hundreds of complex
> multi-legged deals. The price of each of these is VERY expensive to
> compute and each is a relatively isolated computation. A job that
> computes the risk for the whole book needs to skip deals that won't
> price due to coding errors like the one you gave. Yes, I know it's a
> coding error, but these things happen in very large systems.
One obvious solution is to check point, and to provide a means
of continuing from that check point. (Another obvious solution
is to use a good development process that ensures that the
probability of such errors is practically zero. Easier said
than done, however; I work in a similar environment, and I know
that the environment doesn't encourage such a process.)
Having said that, there are a few cases where not crashing is
valid. One, obviously, is if the data isn't important: in a
games program, it's probably better to go on---if you crash
later, it's not worse than crashing immediately, and if you
don't, the player just experiences a quirk in the action.
Another is plug-ins of all sorts: crashing brings down the
complete application, and not just your code, and the rest of
the application may not know that check-pointing was necessary.
(And trying to debug the core dump you get from JVM or Excel is
not a forgone conclusion.) But of course, if you take that
attitude, then you've also got to catch segment violations and
continue as well.
> > The point was not whether assert() is a good way to assert,
> > but rather whether asserting is the right choice in some
> > cases.
> Yes.
> > However, there are two problems with throwing a MyLogicError:
> > 1) The calling code cannot do anything with it. If the query was badly
> > written, you'd need self-modifying code to fix it.
> I disagree. My program *can* do something with it. In my
> example the risk analysis skips that deal and goes on to the
> next one.
Except that there is a non-negligible risk that memory for the
other deals has been corrupted as well. In ways that will give
wrong results. (I'm speaking from experience here, Luckily,
the results were so wrong that they were recognized as such.)
> > 2) You have two *very* different paths while debugging and in
> > production.
> Not with my approach you don't. I would throw a std::logic_error in
> both cases.
Which may cause important information to be lost if you core
dump because of an uncaught exception.
> > I cordially dislike conditional compilation
> So do I.
> > But again, I'm not sure we agree on logic errors. If I pass a nullptr to
> > a library that doesn't expect it, I want it to crash right there and
> > right now so I can know what happens.
> I don't. I want my program to skip to the next deal (and reporting the
> deal failure as a logic error of course).
> I think the point is that there are cases (like mine) where you want
> to be able to trap and handle the error and there are cases where you
> want the program to blow up with a core dump. I am coming to the
> conclusion that the behaviour needs to be configurable. IMO the place
> to define this configuration in that part of the code that owns
> "main". Libraries that may be used by both kinds of program need to
> provide this configuration ability and invoke it from "main". Then we
> will *both* be happy. The trouble is, there is no general purpose way
> to do this so each library will do it differently. This is why, in the
> meantime, I prefer the throwing approach. Programs like yours that
> don't attempt to catch and handle logic errors will still just blow up
> because the exception will bubble up to main.
In theory, VS offers such a choice at compile time: you choose
to use structured exceptions, or you don't. (In practice, I'm
not sure what happens to exception safety if a structured
exception occurs in a nothrow function.)
--
James Kanze
> > Andrew schrieb:
> > > The usual argument is that it is useful to have a core for
> > > debugging. I agree that this is useful for UNIX
> > > environments. It is not so useful on Windoze, where the
> > > expectation seems to be to use the Visual Studio debugger
> > > and run the program again, catching the assertion as it
> > > occurs.
> > I guess this is more an argument against Windows than an argument
> > against asserts, then. Core dumps are extremely useful for analyzing
> > problems.
> Maybe it does just boil down to how useful it is to have a core dump.
That depends on what is in the core dump.
> Part of the answer to that question seems to be 'it is not very useful
> to Windoze programmers but UNIX programmers often find it useful'.
I suspect that the main reason it's not useful to Windows
programmers is that they don't know how to get it, nor how to
use the debugger on it. (i'm currently a windows programmer,
and that's my case, anyway. our clients' machines don't have
visual studios installed on them, and when the program crashes,
i don't get a core dump. But if I did get one, I wouldn't know
how to tell Visual Studios to look at.)
> Maybe the fact that some programmers find it very useful and
> others prefer an exception which they may want to handle (see
> below) means that it should be configurable.
It is, sort of. In most cases, the core dump is preferable, but
there are exceptions. In our Excel plug-ins, we use Microsoft's
structured exceptions, to avoid crashing Excel, even in cases of
segment violations and the like. Because assert isn't the only
issue: if you want to continue in case of assert, you also have
to continue in case of a segment violation, or whatever else may
occur. (A segment violation is, after all, an
assert(isLegal(p)). Implemented in hardware, for a very loose
definition of isLegal.)
Note that Java really doesn't do any better. Except that it
eliminates the core dump, and gives you its idea of what you
want to know, rather than all of the information. (If you've
ever had to track down a problem of JVM crashes on a Linux box,
you know what I mean. A core dump would be nice.)
--
James Kanze
> > I do agree than an assert is more useful than an unhandled
> > exception in production code.
> In user-facing applications, unhandled exceptions are *always* wrong.
> No user on the face of this earth has ever said "Thank God it
> crashed! I didn't want to save my work anyway." The best thing that
> can be done is to tell the user what happened and give them the option
> to (try to) exit gracefully. If they continue on, they assume the
> risk involved.
In user-facing applications, where the user is creating data,
you should be check-pointing often enough that the crash won't
cause any significant loss of data. Given the choice of a crash
and possibly corrupting data, any intelligent user will prefer
the crash.
--
James Kanze
Telling to Bill Gates (sending core dump to microsoft) is default
behavior that can be changed. Set it to send the core dump to real
devs during installation.
> or invoke the debugger, or just accept the crash. When the debugger is
> invoked
> the process state is intact. And when everything works as well as it should,
>
> it's as if you had run the program to this point in the debugger.
It is still available in Windows like everywhere. Bill wants core
dumps to himself by default to load these into debugger himself. That
way if any of his API did participate in crash he can fix it and make
patches. When it is really dangerous what is going on then he can add
the discovered things to "malicious software removal tool" too. ;)
--
Crashing (in the immediate, irrevocable, uncatchable sense of the
word) is (nearly) the worst possible thing your app can do. It is
extremely hostile and - tho you might claim this is irrelevant (it
isn't) - profoundly unprofessional. They lose time, effort, and
possibly stability of the rest of their software ecosystem. What is
better for you the developer is more/less irrelevant -- you aren't the
paying customer.
>
>> The best thing that can be done is to tell the user what happened and
>> give them the option to (try to) exit gracefully.
>
> Ugh, this is assuming that the user reads the message, understands it
> and takes an informed decision. I would rely on very few users for
> this
> kind of decision.
You would rather to make the uninformed decision for them?
User (lack of) knowledge is undeniably a problem -- but then, so is
developer (lack of) knowledge.
>
>> If they continue on, they assume the risk involved.
>
> Well, that's the problem. When you get to a point where you need to
> assert or throw a logic_error, what can you do? Do you take the chance
> of saving the work, possibly corrupting a file or a database? Your
> program is an inconsistent state, all bets are off.
If you waited 2 hours for the results of a operation that was followed
by a mysterious error, you might be willing to take that chance.
>
> Personally, I want to crash right there to make sure I don't format
> the
> drive or send dirty pictures to someone. Then I want to get the core
> dump and see what happened so I can fix it.
Core dumps are only marginally useful. 90% of their usefulness is the
stack trace (if you're lucky enough to get even that) and that can be
gotten other ways that don't end in abort().
Mike
Worst thing however (not only "nearly", the worst) your app can do is
to "catch(...){return;}" on defect and pretend that nothing did
happen. Most hostile (not only "extremely", the most) is to sell
software to your customers with known unfixed defects (and so the
"worst things" to hide these) in it. About "professional" it is hard
to tell ... it feels "cultural". I think i can understand it when the
developer is from a society where lying and keep-smiling when lying is
considered to be normal human behavior.
While I'm with you that crashing in the face of the user isn't very helpful, I would say that core dumps are *extremely* helpful for developers, and our dumps on Windows yield useable stack traces in 90% of all crash cases (and, yes, we do have to much of these).
Note: asserts are disabled in our production releases, but that doesn't get you very far wrt to crashes in C++ :-)
cheers,
Martin
That depends a lot on the software. If you have active support contracts, you can pretty much count on users calling you up if the app crashes, and then it's a matter of telling them to send you the core dump file. (Provided you properly saved it somewhere they can find it.)
> That brings me to a question:
> Is there a way to create a core dump from where you are and then
> continue execute the code?
>
On Windows, you can write a process dump at any point, it's got nothing to do with whether the app has crashed. Though, if it's possible or makes any sense to continue when you have an uncaught error, I'm not sure.
cheers,
Martin
I beg to differ, because:
1. If code is already ready for exceptions in general, then it's
pretty ready for another one.
2. If, additionally, it's ready for an exception it knows not what to
do with, then it's completely ready. That one will bubble up to a last-
ditch catch whose sole purpose is to inform the operator.
I, personally, am of opinion that 2. should be the case always and
anyhow. Consequently, no additional work is needed to test anything.
Goran.
--
In my mind there's another type of error, which I'd call "corruption".
That refers to coding mistakes that cause memory violation
(specifically write to non-owned area in memory like uninitialized
pointer or bound breach). These where not previously verified by
assertion, because such an assertion (e.g. bound check) would have
failed and not let the violation occur. The special problem with this
kind is that the code may have corrupted any other data of other
structures in the process, and generally, all current values in the
process memory cannot be trusted.
The practical point of this difference is as some other posts
suggested that sometimes an assertion could evolve into a throw, so
that a certain wrong calculation can be skipped somehow, and continue
the global task without it. When a corruption error happens there's
never a possibility to skip anything, it is always a show stopper,
because the global task may have been corrupted.
However, it is important to clearly explain the case when an assertion
failure (that always implies a bug in the code) could evolve into such
a throw. The point is that some global task could skip the wrong
calculation. However, it raises the question: if you already know
there's a bug in the code as this assertion failure implies, then how
can you trust any other result, that such a bug could cause, but maybe
the assertion doesn't catch. In other words, most assertions fail *if*
there is a bug in the code that affected the currect calculation, but
when they succeed, it doesn't neccessarily imply that such a bug
didn't affect the calculation.
But in some special cases, it could work. Suppose there's some complex
calculation done by function F(x) (for example break an integer to
primary components). But there's also already a function G(y) which is
simple (relatively non-complex) and is the inverse of F (s.t.
G(F(x))==x).
Then one could implement F:
Result F( Input x )
{
//some complex computation here which results with:
Result const y( ... );
ASSERT( G(y) == x ); //this is a relatively short computation.
return y;
}
This is an "ultimate assertion", it means that it passes *if and only
if* the calculation is correct. So if it passes one can certainly
trust the result. Such an assertion could evolve to a throw:
Result F( Input x )
{
//some complex computation here which results with:
Result const y( ... );
if( G(y) == x ) //this is a relatively short computation.
{
throw BugException( "bug in my code" );
}
return y;
}
This can be caught by the caller task, and insuccessful calculations
can be skipped, while trusting that the calculations that completed
were not affected by any bugs.
* we are looking for bugs in the code for F, the question of whether
there could be a bug in G is valid, but also a separate question - G
has to be debugged for its own sake.
itaj
> In other words, most assertions fail *if*
> there is a bug in the code that affected the currect calculation, but
> when they succeed, it doesn't neccessarily imply that such a bug
> didn't affect the calculation.
>
strictly speaking, most assertions fail *only if* there is a bug ...
but I guess what I meant is clear
There's nothing you can do about errors of omission or external forces
other than perhaps notice the corruption. There is no mechanism to deal
with that. If you need robustness in those situation, you're looking at
doing N-level programming and/or redundant systems, or whatever. That
outer level is where "fault tolerance" is more appropriate terminology
than "error handling".
>
> However, it is important to clearly explain the case when an assertion
> failure (that always implies a bug in the code) could evolve into such
> a throw. The point is that some global task could skip the wrong
> calculation. However, it raises the question: if you already know
> there's a bug in the code as this assertion failure implies, then how
> can you trust any other result, that such a bug could cause, but maybe
> the assertion doesn't catch.
If that assertion (or exception) is in a library, at the point at which
it was noted, the bad thing (potential crash or worse) did not yet
happen, for the assertion (or exception) prevented that. It's not up to
the library developer to reason about application code and how an invalid
argument got passed to the function, but only to respond appropriately.
These kinds of things should be weeded out during development, of course.
In release mode, there are a number of possibilities to enlist upon
error, depending on the application, but of course, those bugs should not
be there.
> In other words, most assertions fail *if*
> there is a bug in the code that affected the currect calculation,
So what? That is the developer's aid to debugging during development and
a process by which programs are debugged.
> but
> when they succeed, it doesn't neccessarily imply that such a bug
> didn't affect the calculation.
Don't know what you mean here.
> Programmers have all sorts of standards they use to define WHEN to
> throw an exception. I use one simple rule: If the function cannot
> fulfill its purpose, throw an exception. That what exception means:
> outside of the domain I am designed to handle.
>
> A common example is a FileOpen function, like this:
>
> int FileOpen(const char* name, int flags)
> {
> int rval = ::open(name, flags);
>
> if (rval == -1)
> {
> /*
> BIG ARGUMENT HERE. Return rval? return 0? throw exception?
All three of those can be seen as the same thing, albeit different
degrees of sophistication. The mechanism used doesn't matter as long as
it is robust, and exceptions get the nod here for most cases and
programmers, they are quite decidedly weighty though. The above three
things are not the only possibilities, of course.
> The caller of this function is depending on getting an open
> file. That's why he called it. If he doesn't get an open
> file we can assume he's screwed too and cannot continue with
> his task.
He cannot continue as if the file opened, you mean. That is not to say he
cannot continue with the task, for the focus should not be on raising
errors, but rather on handling them. At that point in the code, the
programmer was supposed to consider the error codes/exceptions that the
called function may generate and act appropriately (which may mean
propagating the error, but that is not the common thing to do). Maybe the
thing to do, for example, would be to check that the file is there and if
not, then create it and retry the open. Problem solved then if it opens.
Whether that is done in response to a "raise" or in response to the
return code is irrelevant.
> I say the proper thing to do is
> throw an exception. If you follow this philosophy your code
> becomes at least 5x less complex.
Strawman. You've really not said anything except that errors should be
considered and handled. Duh.
>
> > However, it is important to clearly explain the case when an assertion
> > failure (that always implies a bug in the code) could evolve into such
> > a throw. The point is that some global task could skip the wrong
> > calculation. However, it raises the question: if you already know
> > there's a bug in the code as this assertion failure implies, then how
> > can you trust any other result, that such a bug could cause, but maybe
> > the assertion doesn't catch.
>
> If that assertion (or exception) is in a library, at the point at which
> it was noted, the bad thing (potential crash or worse) did not yet
> happen, for the assertion (or exception) prevented that. It's not up to
> the library developer to reason about application code and how an invalid
> argument got passed to the function, but only to respond appropriately.
The case I considered was an assertion that fails due to a bug in the
library itself, not a wrong parameter.
> These kinds of things should be weeded out during development, of course.
> In release mode, there are a number of possibilities to enlist upon
> error, depending on the application, but of course, those bugs should not
> be there.
As been suggested by other posts here (to which I was referring),
there is a practical difference, at least in some situations, between
"bugs should not be there" and "bugs are not there". Don't
misunderstand me, I am the worst proponent for non-safe code, and once
a bug is known to exist it is always the first priority to find and
fix it. But I've had experience of working with teams that had very
few programmers and QA comparing to the size of the code base and
changes. In such cases the management decided to keep the timeline
required for marketing and accepting some fast resolution process for
bugs that occur on the customer site. In such cases ignoring that
difference is pure wishful thinking, OTOH accepting it and taking some
practical measures to go on from there may get the job done faster and
easier
>
> > In other words, most assertions fail *if*
> > there is a bug in the code that affected the currect calculation,
>
> So what? That is the developer's aid to debugging during development and
> a process by which programs are debugged.
>
> > but
> > when they succeed, it doesn't neccessarily imply that such a bug
> > didn't affect the calculation.
>
> Don't know what you mean here.
>
What I meant here would also answer your previous "so what?".
This explains the reason for what I said later. That unlike these most
cases, some cases have a stricter solution where there is a
practically possible "ultimate-assertion" that is *if and only if*,
and the it would be good to notice these cases because they have a
sound solution (as I described).
itaj
Well what are assertions if not an aid to debugging?
> On Mar 10, 1:40 am, Jonathan Mcdougall <jonathanmcdoug...@gmail.com>
> wrote:
> > The point was not whether assert() is a good way to assert, but rather
> > whether asserting is the right choice in some cases.
> 2. If, additionally, it's ready for an exception it knows not what to
> do with, then it's completely ready. That one will bubble up to a last-
> ditch catch whose sole purpose is to inform the operator.
How do you know? Many of your class invariants may no longer be valid
(you detected a programming error, after all), so you cannot predict
that the destructors will do sane things.
Assertions are about getting the system back into a known state;
exceptions are not.
--
Nevin ":-)" Liber <mailto:ne...@eviloverlord.com> 773 961-1620
It starts off as a logic error but is considered an exceptional error by
the main system to be able to skip and continue. In order to make that
work, the logic error must throw and not abort.
Nothing.
But my point in saying that here was only to frame the next section
about "ultimate-assertions" that have the stronger *if and only if*
property here.
itaj
Do you mean that it would be of value to distinguish between failures in preconditions (wrong parameter) and invariants and postconditions (library itself)?
Well, my whole point was about dealing with bugs in the library. I was
referring to something like Andrew described in another branch of this
thread (and maybe I should have put this part of my post there). A
bigger task that uses a library to perform many complex calculations,
and some of them may fail due to a bug in the library, but still
others that succeeded can be used. I was trying to explain how come it
could be possible to trust completed calculation when you know there's
a bug in the library - if there's a simple inverse function, you can
throw on wrong calculations and thus only successful calculations ever
complete.
itaj
I can't understand this reasoning. If the execution of the program is
safety-critical, bugs should not exist in the library. If it is not,
the program must crash to prevent corruption.
I don't think a program should be developed in a way to handle bugs
gracefully. In fact, I don't think it is possible at all.
In the specific case of plugins, running them in a separate process is
a lot better, safer and can handle many more types of errors than
trying to unroll execution and continuing.
> I was trying to explain how come it could be possible to trust
> completed calculation when you know there's a bug in the library -
> if there's a simple inverse function, you can throw on wrong
> calculations and thus only successful calculations ever complete.
What if there's also a bug in the inverse function? What if there's
no inverse function? There might be specific cases where this is
possible, but it sounds convoluted and improbable to me.
--
Jonathan Mcdougall
I agree. It should be tested enough so that the chances of this
happening are as close to zero as possible.
> It is extremely hostile and - tho you might claim this is irrelevant
> (it isn't) - profoundly unprofessional.
We agree that a crashed application is unprofessional, but whether it
comes from an exception, an assert or a good old segfault matters not.
The error handling method used is unimportant. Testing is.
>>> The best thing that can be done is to tell the user what happened
>>> and give them the option to (try to) exit gracefully.
>>
>> Ugh, this is assuming that the user reads the message, understands
>> it and takes an informed decision. I would rely on very few users
>> for this kind of decision.
>
> You would rather to make the uninformed decision for them?
Definitely. And it is not uninformed: I know the program is about to
die.
You seem to assume that when reaching an assert, the execution can
unroll correctly (it may not, the stack might have been corrupted) and
find a suitable handler that can save whatever needs saving.
Writing something to disk needs to create a valid handle and to access
undamaged data. If the backend is more complex, such as involving a
network or a database, then the operation is even more fraught with
danger.
>> Do you take the chance of saving the work, possibly corrupting a
>> file or a database? Your program is an inconsistent state, all bets
>> are off.
>
> If you waited 2 hours for the results of a operation that was followed
> by a mysterious error, you might be willing to take that chance.
Personally, I would not. It's not only a matter of saving damaged
data but of corrupting already existing data.
If your two hour long operation tries to save the results, it might
look like it succeeded. But what if a single bit was flipped and you
end up with a chart that says you sold 214 items instead of 150? What
if this flipped bit is buried into gigabytes of good data?
You cannot trust data that was written by an application that's
crashing.
> Core dumps are only marginally useful. 90% of their usefulness is
> the stack trace (if you're lucky enough to get even that) and that
> can be gotten other ways that don't end in abort().
This is not core dumps vs. exceptions. The point of an assert is not
mainly to get a core dump. It's to stop an application that is on the
verge of misbehaving. If my stack is corrupted and my path variable
got changed from "/tmp/whatever" to "/", I don't want the following
rm to be executed, whether it is the next statement or an automatic
cleanup when the application exits.
And getting a good and useful core dump might be akin to getting a
pony, but that doesn't mean we should give away all hope.
--
Jonathan Mcdougall
However, if your problem is not easily reproducible, you've lost the
possibility of a core dump. It would also force you to recompile,
which may change the code enough so that the bug disappears if it was
caused by timing or other similar issues.
I find the whole "throw exception, if one is thrown change it to an
assert, then re-execute, break, fix the bug, remove the assert,
continue" to be unnecessary complex. Putting an assert in the first
place would have done the same thing.
>> except for a "sorry, the program crashed" message, I don't see how
>> handling a std::logic_error is any better than asserting.
>
> First I see it as a user experience. It might sound stupid, but I
> prefer a "sorry, the program crashed".
Then this is a matter of preference, and I don't. I don't think a
technical user would care, since she would understand that the program
just crashed in both cases. As for a regular user, he wouldn't be able
to distinguish between a crash and a "user friendly" crash notice;
both would result in the "program doesn't work" email.
> If it also can help me save important files before crashing, then
> I'm even happier.
Again, if a program is in an inconsistent state, I don't want it to
save anything. Just crash already. Don't take down the system, don't
corrupt my data, don't try anything fancy, just crash, you're done for
today.
If the program was of high enough quality, it created regular backups
that can be used to recover the work.
> I see another benefit as well. If a plug-in throws instead of core
> dumps, the main program may choose to unload the plug-in instead of
> piping the user's work to /dev/null making him/her go take a coffe.
Yeah, well, that's the same example that's given over and over: if
the program is in fact a system with a collection of independent
module, I don't want the system to fail when a module breaks.
Sure. This is a very specific scenario where what I consider to be
conventional error handling breaks down. Logic errors become runtime
errors. In this case, the safest approach imo would be to use separate
processes (like recent browsers are doing) since the issue is not only
with asserts, but with other not easily handled errors, such as
access violations, divisions by zero or stack overflows.
>>> I could stretch myself to allow core dumps at close beta-test
>>> sites, but is there any benefit at all doing a core dump in
>>> production?
>>
>> Using the core dump to fix the bug?
>
> That's a dream scenario.
Why is that?
> But it demands quite a lot from your logistics to get hold of a
> user's core dump. If you manage to do that in an easy way, that's
> very good.
Why would it hard to get a core dump? Breakpad for example makes it
easy to generate a dump and start a second process for reporting and
is used by Firefox. Windows Error Reporting also works the same way.
These are variations on "asserting", but they all behave the same
way: generate a core dump, kill the application and start another
process to report the problem.
> Is there a way to create a core dump from where you are and then
> continue execute the code?
Depends on the platform. Windows has MiniDumpWriteDump() and some
implementation of libc have backtrace(). Have a look at Breakpad for
the gory details.
--
Jonathan Mcdougall
If you want the best of both worlds, run your main application as a
child process and generate the "sorry, the program crashed" message in
the parent if the child aborts.
> I see another benefit as well. If a plug-in throws instead of core
> dumps, the main program may choose to unload the plug-in instead of
> piping the user's work to /dev/null making him/her go take a coffe.
>
>
>>> I could stretch myself to allow core dumps at close beta-test sites,
>>> but is there any benefit at all doing a core dump in production?
>>
>> Using the core dump to fix the bug?
>>
>
> That's a dream scenario. But it demands quite a lot from your logistics
> to get hold of a user's core dump. If you manage to do that in an easy
> way, that's very good.
Yet another use for the run the application as a child approach, the
parent can offer the user the option of submitting a bug report (with
the dump).
> That brings me to a question:
> Is there a way to create a core dump from where you are and then
> continue execute the code?
That depends on the platform.
--
Ian Collins
Totaly bugs shouldn't exist. However sometimes they do. And we all
want to fix them, but in complex algorythms it might take time, and in
very special circumstances that I was describing, it is possible to
achieve some necessary work done without fixing the bugs, yet without
risking any wrong calculations.
This certainly not in case of corruptions, only in wrong calculations.
Corruptions are always a fatal show stopper.
> I don't think a program should be developed in a way to handle bugs
> gracefully. In fact, I don't think it is possible at all.
>
Shouldn't. It just possible in that very special and rare case that I
described, when you have a simple inverse function for the complex
(and possibly buggy) algorythm.
>
> What if there's also a bug in the inverse function? What if there's
> no inverse function?
These are very different questions.
1) I was talking about debugging F, and using G was the method. G
ofcourse must have been debugged for its own sake. I hoped the point
that it's much less complex was enough to make some sense. If G has
bug we just have to fix it. Asking what if it still has, can also ask
what if your compiler has bugs - they should be fixed. I was only
talking about the special case of F (which has a much simpler inverse
function namely G, OTOH G and the compiler don't have simpler inverse
functions).
It might happen if to solve F you use some involved heuristics to
solve a generaly NP-complete problem for some partial domain that you
believe your heuristics defined well, but you couldn't be totaly sure
that you proved it mathematically, and you never found a practical
case that condtradicts the assumption. You do have a polynomial
inverse function, so you can just use it to verify all the results. If
ever F is invoked on input that was believed to be within the domain
but actually isn't, the function will throw and that specific
calculation can be skipped, while still trusting that any time F
actualy completes, then the result is totaly correct (has been
verified by G).
2) If there's no simple inverse function then it is not part of the
special case I was describing. And I don't know that there's any way
to go other than fix all bugs before you can use the function at all.
> There might be specific cases where this is
> possible, but it sounds convoluted and improbable to me.
Yes, my whole point was to explain that the "throw on assertion
failure and skip bad calculation" cannot be taken lightly, but only in
very rare special cases.
itaj
Yes, you're right regarding the reproducibility. That's why I was asking
for a way to dump and continue.
My idea would not involve recompilation, just switch a flag. However, it
could still cause timing problems as you say.
>> If it also can help me save important files before crashing, then
>> I'm even happier.
>
> Again, if a program is in an inconsistent state, I don't want it to
> save anything. Just crash already. Don't take down the system, don't
> corrupt my data, don't try anything fancy, just crash, you're done for
> today.
>
> If the program was of high enough quality, it created regular backups
> that can be used to recover the work.
>
You have a good point there. However, I try to picture a dream scenario
where I know what data is safe to save. Maybe the different-process
approach discussed below would be a step closer to it.
The bottom line is that I want to create a system that has full control.
I don't like surprises in the code, and if they show up I want to decide
myself what to do with them, i.e. I don't want any distant code to
decide when to shut down the application or not.
Maybe I aim quite high.
>> I see another benefit as well. If a plug-in throws instead of core
>> dumps, the main program may choose to unload the plug-in instead of
>> piping the user's work to /dev/null making him/her go take a coffe.
>
> Yeah, well, that's the same example that's given over and over: if
> the program is in fact a system with a collection of independent
> module, I don't want the system to fail when a module breaks.
>
> Sure. This is a very specific scenario where what I consider to be
> conventional error handling breaks down. Logic errors become runtime
> errors. In this case, the safest approach imo would be to use separate
> processes (like recent browsers are doing) since the issue is not only
> with asserts, but with other not easily handled errors, such as
> access violations, divisions by zero or stack overflows.
>
Yes, we talk about a new level of technical height here.
>> But it demands quite a lot from your logistics to get hold of a
>> user's core dump. If you manage to do that in an easy way, that's
>> very good.
>
> Why would it hard to get a core dump? Breakpad for example makes it
> easy to generate a dump and start a second process for reporting and
> is used by Firefox. Windows Error Reporting also works the same way.
>
> These are variations on "asserting", but they all behave the same
> way: generate a core dump, kill the application and start another
> process to report the problem.
>
I have to experiment more with that. If I automatically can get hold of
a client's core dump to fix a bug, that would change a lot.
As I think more of it, using another process as a check-point in the
main software may be the way to go to create rock solid main frames. If
you (or anyone) have any goods links or references about such technique,
please share them.
>> Is there a way to create a core dump from where you are and then
>> continue execute the code?
>
> Depends on the platform. Windows has MiniDumpWriteDump() and some
> implementation of libc have backtrace(). Have a look at Breakpad for
> the gory details.
>
Thanks for the breakpad tip! It looks promising, I will investigate it.
At least in my line of work, if I can't reproduce the error myself,
then for all practical purposes, there is no bug to fix. A core-dump/
stack-trace tells me where to start looking but that's usually about
it. I need a way to reproduce the problem regardless.
Mike.
I feel like this statement is at the foundation of our disagreement.
If an application crashing due to memory corruption, then I agree,
this statement is obviously correct. If the application is crashing
because of an unhandled exception (!=corruption), then I think the
situation is less clear.
I grant you that distinguishing between the two cases can be
difficult, but as you say, "that doesn't mean we should give away all
hope."
> And getting a good and useful core dump might be akin to getting a
> pony, but that doesn't mean we should give away all hope.
I don't know about where you come from, but around here, people give
away horses for free just so they don't have to pay to feed them
anymore. ;^)
Mike.
In addition we need more reliable OSs that fully isolate processes from
each other. Note that subtle things such as a failure to restore
registers to their exact condition can result in (sometimes rare)
failures. The bug is there (usually as a result of undefined behaviour)
but only bites when a particular set of processes are run with control
passing in a very specific order.
UB is a very serious problem and getting worse as our hardware becomes
more complex. We really need tools that will detect all potential for UB
in a process. At least we do for any mission critical software.
I am reminded of a case many years ago when the corruption (actually
truncation) of a data file (a saved document) fatally crashed MSWord.
That is not funny when the crashing document is several hundred pages
long and the back-ups have also been corrupted. I think that a major
program that completely crashes because a data file has been truncated
has a bug regardless of the fact that the problem is caused by an
external failure.
However this is now getting too far from C++. Just note that the coming
version of C++ is designed to support genuine concurrency though that
does not mean that developers will avoid concurrency caused problems.
Not necessarily ...
I see the distinguishing characteristic of an assertion being the fact
that it is a debugging aid that is present only in debug builds (builds
in which NDEBUG is not defined). What the assertion does when it is
triggered depends on the implementation of assert().
Now, we all know (I hope) that the standard C runtime library contains
an implementation of assert() that writes a message to stderr and
terminates. In most software development I have been involved with in
recent years an "application" is either a GUI thing (be it for Windows
or something built on X) that has no stderr to write to, or is a
background process (Windows would call it a service) that does not have
a stderr to write to ... so if we use the standard C implementation of
assert() we're never going to see the message.
I therefore take it as read (perhaps wrongly) that nobody in their
right mind would ever use the C assert in a real application, and that
the assertions we're all talking about here are a different kind of
animal altogether.
The assertion code that I use in interactive applications asks the user
how it should behave. The user can then elect to enter the debugger,
ignore the assertion and carry on (perhaps after dumping state for
later examination), or terminate the program (with or without a dump).
I sometimes also allow an environment variable to be set to allow the
user to suppress this interaction and automatically choose one of the
options for all or some exceptions (it's handy not to have the
interaction in unit test suites, for example), and I use the
environment variable technique with background processes that cannot
easily interact with the user.
I code the error handling of my application using exceptions. I may
also place assertions in the code either before the exception is thrown
or in the constructor of the exception object. The assertion gives me
the chance to pop into the debugger before the exception is thrown and
see what's going on, but it does not prevent me from allowing execution
to continue and exercising the exception code in debug builds.
In general, I believe that an application should unwind and clean up
behind itself even when exiting after an unrecoverable error -- one
doesn't want to find shared libraries locked in memory after an
uncontrolled exit; or memory, semaphores, etc., shared with other
application left in an inconsistent state after a crash. RAII gives us
a good way to manage these things, and if we are to use the power of
RAII we should be very wary of allowing an application to exit without
unwinding its stack.
OTOH I do agree that there are times when an application may detect
some error in processing that makes it impossible to continue, and in
such a case I would force an immediate exist (with a dump, if possible)
-- this is NOT an assertion, though. I reserve the term "assertion" for
debug code that is omitted from release builds, which this is not. An
"emergency stop" exit of the kind I'm describing here may behave rather
like the standard C assert() but it is conceptually very different.
--
Regards,
Daniel James
[Lest there be any confusion I am NOT the Boost maintainer of the same
name]
Normally, a core-dump-on-crash will tell me *exactly* what went wrong,
as in: "what code crashed the application". And I'd say that in 80% of
all cases, the core dump alone is enough to actually identify a/the
genuine bug and reproduce and fix it. (Of course, I would assume that
this ratio is rather dependent on the specific application.)
Even in the cases where I can't reproduce the behaviour, the core dump
showing me which part of the code crashed, will help me add defensive
measures so that the next time[a] the code crashes at this point, I
*will* be able to identify the root cause.
cheers,
Martin
[a] : Note: There is *always* a next time. :-)
But (I'll assume) we all work with real-world C++ in a real-world
industry, where the plugins we have are the plugins we have and the
plugin-systems we have are the plugin-systems we have and where
re-engineering a plugin system will cost money you first have to find a
customer to pay for and it may not be feasible at all, because the
complexity you add by going from dyn-libs to IPC is much worse that the
odd crash or not-so-reliable-error-handling dyn-lib plugins cost you.
There are many shades of grey to get a compromise btw. the ideal error
handling and the one you can afford.
cheers,
Martin
--
Stop Software Patents
http://petition.stopsoftwarepatents.eu/841006602158/
http://www.ffii.org/
>From my experience, this depends on the project and the context of the
assert. An assertion in a tight loop might have to go away in
production for performance. However, assertions that detect an
inconsistent state and can remain in production should stay, imho.
I find it vital for a program to terminate when it detects what it
considers an unrecoverable problem, such as unexpectedly receiving a
null pointer.
> In most software development I have been involved with in recent
> years an "application" is either a GUI thing (be it for Windows or
> something built on X) that has no stderr to write to [...] so if we
> use the standard C implementation of assert() we're never going to
> see the message.
Some implementations are not standard-compliant when it comes to
assert(). Visual C++ for example uses MessageBox() to display the
message when no console is present, specifically to address the
problem you are mentioning.
> I therefore take it as read (perhaps wrongly) that nobody in their
> right mind would ever use the C assert in a real application, and
> that the assertions we're all talking about here are a different
> kind of animal altogether.
I was referring to "assertion" as a general way of generating a core
dump and terminating the application. On some platforms, this is
exactly what assert() does. Therefore, although I might not be in my
right mind, I do use assert() when it suits my needs.
> The assertion code that I use in interactive applications asks the
> user how it should behave. The user can then elect to enter the
> debugger, ignore the assertion and carry on (perhaps after dumping
> state for later examination), or terminate the program (with or
> without a dump).
By the way, this is the exact implementation of Visual C++'s assert().
It is also something I want to show to a developer, but not a user.
> I sometimes also allow an environment variable to be set to allow
> the user to suppress this interaction and automatically choose one
> of the options for all or some exceptions (it's handy not to have
> the interaction in unit test suites, for example), and I use the
> environment variable technique with background processes that cannot
> easily interact with the user.
I agree that asserts are not unit-test-friendly and that having some
sort of compile time switch to decide between an assertion or an
exception is a good thing in this case because the code executes in
a controlled environment where failure is not only safe, but also
expected.
> I code the error handling of my application using exceptions. I may
> also place assertions in the code either before the exception is
> thrown or in the constructor of the exception object. The assertion
> gives me the chance to pop into the debugger before the exception is
> thrown and see what's going on, but it does not prevent me from
> allowing execution to continue and exercising the exception code in
> debug builds.
This requires discipline and I usually strive to eliminate anything
that relies on discipline. I find it too easy to break, observe and
then terminate.
I also often find myself in need of modifying the current stack to
verify a problem, such as re-executing the previous function call
again to step in and verify its behavior. This might make resuming
impossible and leave the exceptional case untested.
While writing exception-safe code and unit tests makes this less of a
problem, it still leaves the possibility of an untested, or
under-tested, execution path.
> In general, I believe that an application should unwind and clean up
> behind itself even when exiting after an unrecoverable error
This assumes that although the assert was triggered, unwinding is
still a valid option. It may not for technical (such as stack
corruption) or semantic (doing a sensitive operation on a corrupted
object) reasons.
> one doesn't want to find shared libraries locked in memory after an
> uncontrolled exit; or memory, semaphores, etc., shared with other
> application left in an inconsistent state after a crash.
However, one doesn't want to take down a flaky system by giving it
invalid handles. I've had QNX freeze on me more than once by trying
to destroy invalid OpenGL handles.
> OTOH I do agree that there are times when an application may detect
> some error in processing that makes it impossible to continue, and
> in such a case I would force an immediate exist (with a dump, if
> possible) -- this is NOT an assertion, though. I reserve the term
> "assertion" for debug code that is omitted from release builds,
> which this is not.
Hm. Then we disagree with what an "assertion" is. For me, it is a
sanity check. Whether it is compiled in or not does not matter. If
the check doesn't pass, the program is in an invalid state and needs
to be terminated.
If the program is allowed to continue because the assertion was
compiled out, then it becomes dangerous to the system and the user.
As an example, I've worked on motion-control programs where if the
program encounters even the most innocuous-looking assertion, it needs
to terminate immediately. If it doesn't it might move a five-ton
trolley the wrong way and kill someone. Terminating it cuts the
connection with the motion-control hardware, which puts the machine in
a safe mode.
Although this is a rather radical example, I find this concept to be
important even in banal programs, where an innocuous-looking assertion
might mean data corruption or system instability.
--
Jonathan Mcdougall
I agree wholeheartedly, but sometimes I have the impression that a lot of people posting here actually use the "classical" stderr+abort assert() macro.
> The assertion code that I use in interactive applications asks the user
> how it should behave. The user can then elect to enter the debugger,
> ignore the assertion and carry on (...)
>
It's not clear from your post if you're on Windows and whether you're using the ASSERT() macro, but note that to display a choice to the user in an interactive app, you need to display a dialog and this dialog needs (Window-)message processing which can create really weird behaviour because the ASSERT dialog is processing messages while the user is reading it, possibly triggering more code. It tends to work well enough, but I had my share of weird behaviour with the Windows ASSERT dialog.
>
> In general, I believe that an application should unwind and clean up
> behind itself even when exiting after an unrecoverable error -- one
> doesn't want to find shared libraries locked in memory after an
> uncontrolled exit; or memory, semaphores, etc., shared with other
> application left in an inconsistent state after a crash.(...)
Well. I believe you have to trust your OS to do this. If the OS doesn't clean up after a process, you're in deep trouble anyway. (Note that in 5+ years of Windows XP C++ programming, I have see my share of resource leaks in-app and a few GDI resource bugs, but never have I had problems with the OS not cleaning up kernel objects.)
I'm sure there are still a few environments where you can leave the "OS" in a messed up state, but for normal mainstream OSes I would tentatively file what you describe above as cargo cult programming.
cheers,
Martin
--
Stop Sofware Patents
http://petition.stopsoftwarepatents.eu/841006602158/
http://www.ffii.org/
I think a lot of people would disagree with assert debugging aid that is present only in debug builds. Exceptions are used for exceptional conditions, asserts for impossible ones or irrecoverable ones. I have seen a number of (null pointer) assertions from my compiler, so its authors appear to agree.
I guess how asserts are used is partly determined by the type of application. As an embedded developer, I use asserts to protect the system being controlled by my software from entering an indeterminate state. I often use assertions in drivers to verify hardware behaviour and I have seen these fire. As programmers we have to assume correct behaviour of devices or libraries we use. There are error conditions we can't handle or even foresee, so there is a role for assert in production systems.
> In general, I believe that an application should unwind and clean up
> behind itself even when exiting after an unrecoverable error -- one
> doesn't want to find shared libraries locked in memory after an
> uncontrolled exit; or memory, semaphores, etc., shared with other
> application left in an inconsistent state after a crash. RAII gives us
> a good way to manage these things, and if we are to use the power of
> RAII we should be very wary of allowing an application to exit without
> unwinding its stack.
>
> OTOH I do agree that there are times when an application may detect
> some error in processing that makes it impossible to continue, and in
> such a case I would force an immediate exist (with a dump, if possible)
> -- this is NOT an assertion, though. I reserve the term "assertion" for
> debug code that is omitted from release builds, which this is not. An
> "emergency stop" exit of the kind I'm describing here may behave rather
> like the standard C assert() but it is conceptually very different.
Is it? Not if you reserve asserts for "this should never happen" situations.
--
Ian Collins
Note that the Windows `ASSERT()` macro doesn't seem to pose too much
problems with Boost.Test as Boost.Test handles it halfway decently by
default. (I never tried with `assert()`.)
cheers,
Martin
--
Stop Software Patents
http://petition.stopsoftwarepatents.eu/841006602158/
http://www.ffii.org/
Well, I'm not sure. `assert()` (and `ASSERT()`) are both controlled by
the NDEBUG macro which thends to be defined in the stuff that is shipped
to customners, or isn't it?
>Exceptions are used for exceptional
> conditions, asserts for impossible ones or irrecoverable ones. I have
> seen a number of (null pointer) assertions from my compiler, so its
> authors appear to agree.
>
> (...)There are error conditions we
> can't handle or even foresee, so there is a role for assert in
> production systems.
>
The null-pointer assert you mention is however a good example of where
the runtime system will catch the error even without assert. (I assume
on most systems accessing a NULL pointer will terminate the process just
as well as an assert() will(?)
cheers,
Martin
--
Stop Software Patents
http://petition.stopsoftwarepatents.eu/841006602158/
http://www.ffii.org/
I've never encountered ASSERT(), but I certainly leave assert() enabled
in the code I ship. Why would something that's an error in development
not be an error in production?
>> Exceptions are used for exceptional
>> conditions, asserts for impossible ones or irrecoverable ones. I have
>> seen a number of (null pointer) assertions from my compiler, so its
>> authors appear to agree.
>>
>> (...)There are error conditions we
>> can't handle or even foresee, so there is a role for assert in
>> production systems.
>
> The null-pointer assert you mention is however a good example of where
> the runtime system will catch the error even without assert. (I assume
> on most systems accessing a NULL pointer will terminate the process just
> as well as an assert() will(?)
But without the diagnostic message assert provides. To the developer, a
customer report of "it gave a SEGV" isn't much use.
--
Ian Collins
Presence of assert() in compiled code can be switched on and off even
during single translation unit. Header <cassert> does not have
multiple inclusion guards exactly for that purpose. NDEBUG tends to be
defined where and when developer has defined it. What can be reason to
define it somewhere? May be reported performance issue that has been
profiled to be partly because of too lot of asserts.
--
--
No one would argue so. The problem is that, if assertions are meant to
be kept in production code, developers may feel less comfortable to use
them liberally. And the essence of assertions is that it may reveal
something that seems so obviously and trivially true actually turns out
to be false. If developers get concerned about the runtime performance
in production code, they may become frugal in assertions and skip checks
that seem obviously true.
--
Seungbeom Kim
I think you haven't understood -- and I may not have stated sufficiently
clearly -- that my point is that the distinguishing characteristic of
term "assertion" is that it is a mechanism that is present in debug
builds but is always removed from release builds.
That is: calling the function assert() (or using the macro ASSERT) is a
way to document that the test is not performed in release builds. This is
a useful distinction that is (or should be) understood by most
programmers. If you use the term "assertion" to describe checks that are
included in release builds you lose the value of that documentation.
Of course, if you feel that you need to have a runtime check that
immediately terminates the application when it fails then by all means
write one -- but to call it an assertion is counterproductive.
> I find it vital for a program to terminate when it detects what it
> considers an unrecoverable problem, such as unexpectedly receiving a
> null pointer.
Well, of course, that *shouldn't* ever happen ...
My preferred action in such cases would (in all cases I can think of
offhand) be to create a diagnostic dump of execution state for later
examination and throw an exception, so that other parts of the
application have a chance to (try to) exit gracefully.
> I was referring to "assertion" as a general way of generating a core
> dump and terminating the application.
.. and my point was that this is not (or should not be) the
distinguishing characteristic of an assertion.
> > The assertion code that I use in interactive applications asks the
> > user how it should behave. The user can then elect to enter the
> > debugger, ignore the assertion and carry on (perhaps after dumping
> > state for later examination), or terminate the program (with or
> > without a dump).
>
> By the way, this is the exact implementation of Visual C++'s assert().
> It is also something I want to show to a developer, but not a user.
My implementations allow more options than VC's (such as generating a
diagnostic dump to a file and continuing execution) but, yes, I suspect
MFC's implementation of ASSERT is where I first saw the practice.
The fact that this is something that should be shown to a developer but
not to a user is precisely why I say that assertions should not be
enabled in release builds.
Note that I am not saying that one shouldn't make a check in release
builds ... just that a debug build may perform an assertion /before/ the
check that is made in a release build. As the assertion can always be
ignored the release code path can always be exercised in debug builds.
Yes, it requires some discipline ... but ensuring code coverage in tests
*always* requires discipline. This doesn't add much to the burden.
> > In general, I believe that an application should unwind and clean up
> > behind itself even when exiting after an unrecoverable error
>
> This assumes that although the assert was triggered, unwinding is
> still a valid option. It may not for technical (such as stack
> corruption) or semantic (doing a sensitive operation on a corrupted
> object) reasons.
Yes -- and that's why I wrote "in general". There will always be special
cases in which general practices must be set aside.
I must say that in my experience programmers hardly ever make checks for
the sort of technical/semantic error that you describe unless they are
looking for a specific problem in the code -- in this case they'll be
running a debug build anyway and the checking code they insert is
properly described as an assertion. If you feel you still need such
checks in a release build you are either dealing with a special case or
your confidence in your code quality is not high enough for the code to
be released.
> Hm. Then we disagree with what an "assertion" is.
It seems we do, yes. That's my whole point, here.
> If the program is allowed to continue because the assertion was
> compiled out, then it becomes dangerous to the system and the user.
So make a check ... but don't call it an assertion.
I could rant a lot longer here ... instead I'll suggest that you read the
first chapter of Matthew Wilson's /Imperfect C++/ where he dwells on
these matters at some length and gives advice broadly similar to mine.
Regards,
Daniel.
[Lest there be any confusion I am NOT the Boost maintainer of the same
name]
--
I'm using my own ASSERT macro(s) on a mixture of Windows and Linux (and
potentially other things).
Some of these are commandline apps, so the ASSERT interacts with the
user using cerr ... but you're quite right about the message loop issues
that can occur MessageBox and the like.
> Well. I believe you have to trust your OS to do this. If the OS
> doesn't clean up after a process, you're in deep trouble anyway.
IME Windows is pretty good at cleaning up (at least, NT-based versions
are) ... but other OSes are not necessarily so. I've seen problems of
this sort on Linux (though I suspect the Window Manager was the cause,
not the OS itself).
I'd say it was good practice to clean up what you can, just in case the
OS can't.
--
Regards,
Daniel.
[Lest there be any confusion I am NOT the Boost maintainer of the same
name]
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
You seem to have a very individual view of assert. I know many
developers who happily leave some asserts in release code. Just because
it is release code does not mean it is bug free (don't we all wish) and
that one has no desire to remove bugs.
All that assert documents is a condition that cannot happen unless there
is a bug.
<snip>
> I could rant a lot longer here ... instead I'll suggest that you read the
> first chapter of Matthew Wilson's /Imperfect C++/ where he dwells on
> these matters at some length and gives advice broadly similar to mine.
I am unaware of Matthew's infallibility. He is entitled to an opinion
and his experience entitles him to respect but it does not make all
other opinions invalid.
--
I've never heard of such a definition before. You seem to have two
different types of checks: one during development and one in
production. I'd be curious to know what you call the latter.
> That is: calling the function assert() (or using the macro ASSERT)
> is a way to document that the test is not performed in release
> builds. This is a useful distinction that is (or should be)
> understood by most programmers.
You've stated several times now that programmers who do not agree with
your definitions do not have a proper understanding of the language or
of its idioms. I am not sure this is the correct approach.
> If you use the term "assertion" to describe checks that are included
> in release builds you lose the value of that documentation.
As far as I can understand, assertions are valid in whatever build you
are using. Pre- and post-conditions do not change in production.
Whether asserts are compiled out or not does not change the fact that
they describe the invariants of a particular code segment.
>> I find it vital for a program to terminate when it detects what it
>> considers an unrecoverable problem, such as unexpectedly receiving
>> a null pointer.
>
> Well, of course, that *shouldn't* ever happen ...
>
> My preferred action in such cases would (in all cases I can think of
> offhand) be to create a diagnostic dump of execution state for later
> examination and throw an exception, so that other parts of the
> application have a chance to (try to) exit gracefully.
Yes, that is one of the methods described in this thread. I've already
given my opinion on it elsewhere.
>> I was referring to "assertion" as a general way of generating a
>> core dump and terminating the application.
>
> .. and my point was that this is not (or should not be) the
> distinguishing characteristic of an assertion.
You seem to say that the distinguishing characteristic of an assertion
is the fact that it is compiled out during production. I say it is a
check on invariants that decide whether the program can continue
safely or not.
We may be fighting over words here. Do you use a specific term for the
kind of assert I describe?
[on abort/debug/continue dialogs:]
> The fact that this is something that should be shown to a developer
> but not to a user is precisely why I say that assertions should not
> be enabled in release builds.
I think this is not an argument against assertions in production. It
is about the specific behavior of assertions in different
circumstances.
I want my assertions to crash, always. Whether the crash breaks into
a debugger, generates a core dump or just aborts depends on how the
program is executed who executes it. When I'm developing, I don't want
WER to step in when I crash, but I do want it when a user executes it.
But again, we might be agreeing on that although we call this check
by a different name.
>>> In general, I believe that an application should unwind and clean
>>> up behind itself even when exiting after an unrecoverable error
>>
>> This assumes that although the assert was triggered, unwinding is
>> still a valid option. It may not for technical (such as stack
>> corruption) or semantic (doing a sensitive operation on a corrupted
>> object) reasons.
>
> Yes -- and that's why I wrote "in general". There will always be
> special cases in which general practices must be set aside.
I am not sure how the programs I write could be able to distinguish
between safe and unsafe situations to unwind. Therefore, I always
assume that when an assert is hit, unwinding is unsafe, as would be
ignoring it.
> I must say that in my experience programmers hardly ever make checks
> for the sort of technical/semantic error that you describe unless
> they are looking for a specific problem in the code
Agreed. In fact, I don't think it is possible at all, unless you are
working with a very specific case of a variable getting corrupted.
> I could rant a lot longer here ... instead I'll suggest that you
> read the first chapter of Matthew Wilson's /Imperfect C++/ where he
> dwells on these matters at some length and gives advice broadly
> similar to mine.
Thanks for the book suggestion.
--
Jonathan Mcdougall
That is where a lot of developers will disagree with you.
> That is: calling the function assert() (or using the macro ASSERT) is a
> way to document that the test is not performed in release builds.
assert() is a macro, not a function.
> This is
> a useful distinction that is (or should be) understood by most
> programmers. If you use the term "assertion" to describe checks that are
> included in release builds you lose the value of that documentation.
Why? What do I loose by asserting a register must be in a given state when a particular event happens?
> Of course, if you feel that you need to have a runtime check that
> immediately terminates the application when it fails then by all means
> write one -- but to call it an assertion is counterproductive.
Is it? Says who?
>> I find it vital for a program to terminate when it detects what it
>> considers an unrecoverable problem, such as unexpectedly receiving a
>> null pointer.
>
> Well, of course, that *shouldn't* ever happen ...
>
> My preferred action in such cases would (in all cases I can think of
> offhand) be to create a diagnostic dump of execution state for later
> examination and throw an exception, so that other parts of the
> application have a chance to (try to) exit gracefully.
How can an application know it can exit gracefully when it is in an inconsistent state? Is it better to write potentially corrupt data, or none at all?
>> I was referring to "assertion" as a general way of generating a core
>> dump and terminating the application.
>
> ... and my point was that this is not (or should not be) the
> distinguishing characteristic of an assertion.
But it is the standard interpretation of an assertion.
--
Ian Collins
But what if the computation produces a meaningful but erroneous answer? Surely the main purpose of an assertion is to prevent an application continuing in an inconsistent state?
--
Ian Collins
Hmmm ... since this is mentioned the second time in this thread, I would be interested in how a project where this is facilitated manages the assert()s that stay in release vs. the asserts that are comiled out?
NDEBUG per file? Freely #define/#undefine NDEBUG?
> All that assert documents is a condition that cannot happen unless there
> is a bug.
>
True. But I too find it confusing to have something called assert() be included in release builds. (Probably just depends on where you come from and what you work with.)
What I would find even more confusing is having some assert() in the code and some not.
So somehow Daniel has a point, at least I see it this way:
1 - assert() depends on NDEBUG
2 - NDEBUG is defined for release builds (at least that's the default of VC++ - don't know about other defaults).
3 - This tends to compile out assert() from release builds.
4 - To have *some* assert()s active and some not, one has to fiddle with the define/undefine of NDEBUG, and I at least consider this rather maintenance unfriendly.
5 - I'd find it much friendlier to have a second macro - which neccessarily would need to be named otherwise - to be left in release builds, and if it's not named assert() because that name's already taken, talking about assert() would then again imply debug build. :-)
Oh, and note that I just checked and MS's ASSERT and _ASSERT/_ASSERTE depend on _DEBUG being present and not on NDEBUG being absent. Whatever that implies for this discussion. :-)
cheers,
Martin
I agree and don't forget that few C++ programmes run in isolation, the bug may be in the hardware of software the programme relies upon. One of my favourites was an assertion in a device driver documenting the expected hardware state when a particular interrupt fired. Some wanted it disabled, but I left it in and it fired in the wild. The hardware had a bug which would have caused bad things (TM) to happen if it had remained undetected.
--
Ian Collins
There are many inconsistent states that it's possible to recover from.
Writing to a random array index is not one of them, but, e.g., unhandled
case in switch() is:
switch (...)
{ default: assert(!"unhandled case"); return meaningless_answer; }
- or -
assert(condition);
if (!condition) { return meaningless_answer; }
You might argue that such is better suited for exceptions, but that would only
make the code more complicated in this case, since I would then have TWO ways
of returning a value from the computation and I would have to decide what the
"meaningless answer" should be each time I call the function. Usually, I
return a status code and fill in the real result through a pointer sent as
function parameter. It's easier to achieve consistency when you assert() at a
single place and also produce some meaningless result there and then (e.g.
return empty set).
And yes, I eat my own dogfood. I never run the "release" version of the app.
--
Given the above, I assume you don't leave assertions enabled in the
"release" version. So you can never test the handling of the returned
"meaningless answer"?
--
Ian Collins
Oh, yes, so it is ... my mistake. I'd forgotten that because, as I did
say, I don't use cassert but rather roll my own more sophisticated
assertions.
Thanks for the correction. Fortunately the error doesn't have any
bearing on my main argument.
> How can an application know it can exit gracefully when it is in an
> inconsistent state? Is it better to write potentially corrupt data,
> or none at all?
Sometimes it may be better to discard the data (perhaps because it can
be recalculated when the bug is fixed), sometimes it may be better to
write it anyway and hope (for example when it is the only copy in
existence of the data from some telemetry that would be impossible or
very expensive to capture again).
Of course, if the data may be corrupt and there is no way to tell (by
inspecting the data /post facto/) whether it is corrupt or not then
discarding it may be the only option.
If the risk of writing bad data over good is your problem, the program
should probably have backed up the good data before it started to make
alterations.
> > ... my point was that this is not (or should not be) the
> > distinguishing characteristic of an assertion.
>
> But it is the standard interpretation of an assertion.
Not in this manor, squire ... and please see my comment on the
"principle of least surprise" in my reply to Francis.
--
Regards,
Daniel.
[Lest there be any confusion I am NOT the Boost maintainer of the same
name]
I tend to have one check called ASSERT that is active only in debug
builds, and another called ENSURE that is active in all builds. In Debug
builds ENSURE calls ASSERT (which allows one to enter the debugger,
etc., and/or to continue) before doing its own thing.
It's always clear when reading the code which checks will remain in
release builds and which will not.
> > That is: calling the function assert() (or using the macro ASSERT)
> > is a way to document that the test is not performed in release
> > builds. This is a useful distinction that is (or should be)
> > understood by most programmers.
>
> You've stated several times now that programmers who do not agree with
> your definitions do not have a proper understanding of the language or
> of its idioms. I am not sure this is the correct approach.
Oh dear. I certainly don't intend to suggest any deficiency on the part
of those others for whom this way of looking at assertions seems to be a
surprise -- if anything the deficiency is mine because I had not
imagined that there would be any significant disagreement.
My sincere apologies to any who may feel that they have been slighted.
> As far as I can understand, assertions are valid in whatever build you
> are using. Pre- and post-conditions do not change in production.
> Whether asserts are compiled out or not does not change the fact that
> they describe the invariants of a particular code segment.
Yes, yes, of course /that/ aspect of an assertion remains in all builds
(how could it not, it's an attribute of the source and not of the
binary). That applies whatever you call it: assertion, precondition, or
whatever.
The point I've been making is that we programmers (in my experience, it
seems that some people's experience may have been different) see the
word "assert" (or ASSERT) in code and assume without thinking that the
condition that assertion expresses will be checked at runtime in debug
builds but not in release builds. We don't (again, IME) imagine that
anyone will use those words to describe a check that is actually
evaluated at runtime in release builds -- to do so violates the
principle of least surprise, and detracts from the readability of the
code.
> We may be fighting over words here.
Not fighting I hope ... but, yes, I think it's only over words that
we're having trouble.
> Do you use a specific term for the kind of assert I describe?
Other than to say that what you describe isn't something that I'd call
an assertion at all? I might say "sanity check", "invariant check",
"forced invariant" or "forced precondition"?
In general I think I'd just call it a runtime check.
As noted above, I sometimes use a macro named "ENSURE" to express such a
check in code (not that it is not spelt "ASSERT", because to me that
would imply that the check was not made in release builds).
Unfortunately "ensurances" doesn't trip off the tongue as easily as
"assertion" <frown>.
> I think this is not an argument against assertions in production. It
> is about the specific behavior of assertions in different
> circumstances.
No, you were right the first time ... it's about words. When I hear
"assertion" I understand it to mean -- specifically -- a check that
won't be active in a release build. There are checks that will be made
at runtime in release builds, but I give them different names.
I had thought that was pretty common practice.
> I want my assertions to crash, always. Whether the crash breaks into
> a debugger, generates a core dump or just aborts ...
Words again: to me "crash" is what a program does when it is completely
out of control -- just as with a car crash -- the driver (user) no
longer has any say in what happens. If you can still get to the debugger
or generate a dump you haven't lost enough control for it to be a real
crash.
I'm not suggesting that you're wrong to use the word "crash" here, just
that I wouldn't have understood you without the additional context (and
I may have misunderstood others elsethread for the same reason).
> I am not sure how the programs I write could be able to distinguish
> between safe and unsafe situations to unwind. Therefore, I always
> assume that when an assert is hit, unwinding is unsafe, as would be
> ignoring it.
This is incidental, at best, to the point I was making about (what I, at
least, call) assertions, and I very largely agree with you.
--
Regards,
Daniel.
[Lest there be any confusion I am NOT the Boost maintainer of the same
name]
That's a specious argument.
Per-source-file build configuration leads to SCM madness. And if
you're distributing a library, now you have to distribute two instead
of one (one with, one without) -- doubling the QA testing. More SCM
madness.
Practically speaking, there is no choice.
Mike.
I know it's not universal, but I think it's fairly widespread. I'm
rather surprised not to find it more widely held here.
> I know many developers who happily leave some asserts in release
> code. Just because it is release code does not mean it is bug free
> (don't we all wish) and that one has no desire to remove bugs.
Again: I'm not suggesting that there shouldn't be runtime checks in
release code, just that it is not helpful to think of these checks as
assertions. Your mileage may vary, as they say.
I have, in one project, used a macro called ENSURE that could be used
like ASSERT but which always checked its condition and behaved non-
interactively (always creating a report in a file) when NDEBUG was
defined.
> All that assert documents is a condition that cannot happen unless
> there is a bug.
In one sense, yes ... but the use of assert() also documents that the
condition will not be checked at runtime if NDEBUG is defined. That's
something that experienced C programmers know, and something that they
instinctively generalize to apply to all assertions -- anything whose
name includes assert (or ASSERT).
Also, IME, NDEBUG is usually either defined globally for a project or
it is not, so a particular build is either a "debug build" (without
NDEBUG) or it is a "release build" (with NDEBUG). In a "release build"
a programmer expects that assertions /sensu lato/ will generate no
code. To write assertions of which this is not true is to violate the
principle of least surprise.
[ Yes, I know assert.h (or cassert) can be included multiple times and
that NDEBUG can be defined and undefined multiple times in a
compilation unit but I have never done this, and have very rarely seen
it done. ]
> > I'll suggest that you read the first chapter of Matthew Wilson's
> > /Imperfect C++/ where he dwells on these matters at some length
> > and gives advice broadly similar to mine.
>
> I am unaware of Matthew's infallibility. He is entitled to an opinion
> and his experience entitles him to respect but it does not make all
> other opinions invalid.
I cited /Imperfect C++/ not because of any strange notion of
infallibility on the part of the author, but because the arguments made
there are similar to those that I might make and it seemed superfluous
duplicate that effort here.
--
Regards,
Daniel.
[Lest there be any confusion I am NOT the Boost maintainer of the same
name]
So do I -- I call them "unrepentant C programmers". >^)
> All that assert documents is a condition that cannot happen unless there
> is a bug.
I categorically dispute this, uh, assertion.
An assert documents a condition that you *believe* to always be true.
E.g. I use (debug-only) asserts liberally within my own code -- there
are very few of my functions longer than one line that do not contain
at least one assert, usually more.
Some of them are timeless & universal (e.g. pointer p cannot be NULL
here because I dereference it on the next line, etc.) -- but in truth
these cases are usually served better by exceptions, not assertions.
The rest are *practically* invariant -- the code was written to
address a certain specific problem domain and the assertion detects
when the current environment falls outside that domain. As the system
evolves however, that assertion may no longer hold true (the problem
domain has expanded/moved yet the assertion has not been updated to
accept it). The code surrounding the assertion may not even require
any modification. I.e. the asserted expression itself may just be
_wrong_.
Sometimes I even write assertions that I know are theoretically wrong
but only in certain rare valid-but-exceptional situations that I
cannot otherwise easily identify. If those situations arise, I want
to know about it so I can verify the situation, disregard the (debug)
assertion, and move on.
In summary, I use assertions to test for incorrect assumptions moreso
than to test for exceptional conditions (that's what exceptions are
for). I treat it as a form of documentation more than quality-
assurance. As can happen with all forms of documentation, it
sometimes falls out of date (even if only in rare corner-cases). The
difference is, my documentation actively nags me when it gets too far
out of date.
This is why my assertions are debug only.
Mike
I will reiterate my opinion that to me, this whole
assert-on-case-by-case sounds pretty horrible as well from an aesthetic
POV as from a maintenance POV.
How's such code to look?
Example 1: (made up by me)
...
#pragma push_macro("NDEBUG")
#undef NDEBUG
#include <cassert>
void function_that_always_uses_assert() {
...
}
#pragma pop_macro("NDEBUG")
That makes four (4) extra lines to switch assert() to the right state
for a function.
Example 2: (made up by me)
...
// NOTE: frobnicate has been tuned for NDEBUG (assert
// turned off), so make sure that the build system
// always defines NDEBUG for release builds of
// this translation unit.
void frobnicate() {
...
}
Another four (4) lines to put a meaningful comment there.
I don't see this working well.
cheers,
Martin
--
Stop Software Patents
http://petition.stopsoftwarepatents.eu/841006602158/
http://www.ffii.org/
I'm not the previous poster, but my employer uses a similar scheme. We
call the latter types of checks "verifications, and have a macro SCVERIFY
to express them.
This was inspired by Microsoft's VERIFY macro, but is different. VERIFY
evaluates its argument in release builds, but throws away the result.
SCVERIFY both evaluates and checks its argument. (The "SC" is a library
prefix.)
> You seem to say that the distinguishing characteristic of an
> assertion is the fact that it is compiled out during production.
> I say it is a check on invariants that decide whether the program
> can continue safely or not.
These aren't far apart. If an assert is compiled out of release builds,
then it can't affect the semantics of the program, so checks are all it
can do.
> I want my assertions to crash, always.
That might be nice, but in my opinion it ought to be too expensive. By
that I mean, if your checks are cheap enough to enable in production
builds, you aren't checking enough. For me this is a big part of the
value of having release and debug builds in the first place.
(And I say "might", because there is an on-going argument between "design
by contract" and "defensive programming", and I currently have a foot in
both camps. SCVERIFY gives the user the option to continue execution.)
-- Dave Harris, Nottingham, UK.
--
But you have raised a point that has been bugging me for long. Even "obviously
correct" code might produce incorrect results, if for no other reason, then
becuase it triggered a bug in the optimizer. How to cope with "undetectable"
subtly wrong results (think of Pentium FDIV bug)? How do you assert() on
something when the very machinery for computing that something might not work
properly? (The optimizer WILL reuse results, if possible, when computing the
assert expression. So it's dubious whether leaving asserts enabled in optimized
builds is to any avail.)
--
We agree on this, there was indeed a misunderstanding on the names
used.
I've said earlier that on projects where assertions stayed enabled in
production (not all of my projects did, depending on the conventions
used at the time), those that popped up during profiling were
definitely disabled. Where I have used assert() and
expensive_assert(), you seem to have used ENSURE and ASSERT. I have
no problem with this.
>>> That is: calling the function assert() (or using the macro ASSERT)
>>> is a way to document that the test is not performed in release
>>> builds. This is a useful distinction that is (or should be)
>>> understood by most programmers.
>
>> You've stated several times now that programmers who do not agree
>> with your definitions do not have a proper understanding of the
>> language or of its idioms. I am not sure this is the correct
>> approach.
>
> Oh dear. I certainly don't intend to suggest any deficiency on the
> part of those others for whom this way of looking at assertions
> seems to be a surprise -- if anything the deficiency is mine because
> I had not imagined that there would be any significant disagreement.
>
> My sincere apologies to any who may feel that they have been
> slighted.
We saw a significant decrease in animosity between certain programmers
when we instituted the 10-meter rule for emails: if the office of the
recipient is less than 10 meters away, walk over.
So, no worries. Tubes will do that.
>> We may be fighting over words here.
>
> Not fighting I hope ... but, yes, I think it's only over words that
> we're having trouble.
Darn. I was about to get my sword out.
>> As far as I can understand, assertions are valid in whatever build
>> you are using. Pre- and post-conditions do not change in
>> production. Whether asserts are compiled out or not does not change
>> the fact that they describe the invariants of a particular code
>> segment.
>> [...]
>> Do you use a specific term for the kind of assert I describe?
>
> Other than to say that what you describe isn't something that I'd
> call an assertion at all? I might say "sanity check", "invariant
> check", "forced invariant" or "forced precondition"?
>
> In general I think I'd just call it a runtime check.
Fair enough. We also seem to agree that these "runtime checks" should
probably prevent the program from continuing (which in most cases
implies not saving the latest data), get some kind of core dump (and
perhaps break into a debugger) and exit as cleanly as possible (which
may not be possible at all.)
--
Jonathan Mcdougall
--
My point was if you never run a release version during the development phase, how can you test the error handling code? It will never be reached because the assert will fire. You wrote
assert(condition);
if (!condition) { return meaningless_answer; }
so the if will never be reached unless asserts are disabled.
--
Ian Collins
Generally I agree there that such things are rarely needed. What i
wanted to say ... don't define NDEBUG as rule. Define it as exception.
Define it only for something where assertions affect performance in
inner loop of inner loop so your profiler shows it.
> How's such code to look?
>
> Example 1: (made up by me)
> ...
> #pragma push_macro("NDEBUG")
> #undef NDEBUG
> #include <cassert>
> void function_that_always_uses_assert() {
> ...}
>
> #pragma pop_macro("NDEBUG")
>
> That makes four (4) extra lines to switch assert() to the right state
> for a function.
Make special headers, then it is 2 lines. Also performance
optimizations should be commented in code.
// FIX issue XXC06327894: performance is crap because of
// asserts in function_that_should_not_assert_in_release()
#include "no_asserts_in_release.h"
void function_that_should_not_assert_in_release()
{
/* ... */
}
#include "assert_like_always.h"
--
So don't do that. It's entirely possible to have fine-grained control
of assert without changing the build configuration for different
source files. It's also possible (and sometimes quite useful) to have
a control at a finer level than per translation unit.
To do that, I would rarely use the assert macro directly. I would
almost always wrap it in a set of macros called FOO_ASSERT (where FOO
is the name of the library / subsystem / component). They're defined
somewhere in a library-specific header that allows you to switch
between various behaviours:
#define ASSERT_MODE_NONE 0
#define ASSERT_MODE_ABORT 1
#define ASSERT_MODE_CASSERT 2
#if FOO_ASSERT_MODE == ASSERT_MODE_NONE
# define FOO_ASSERT(expr) ((void)(expr))
#elif FOO_ASSERT_MODE == ASSERT_MODE_ABORT
# include <stdlib.h>
# define FOO_ASSERT(expr) ((void)(expr), abort())
#elif FOO_ASSERT_MODE == ASSERT_MODE_CASSERT
# include <assert.h>
# define FOO_ASSERT(expr) assert(expr)
#else
# error Unknown value of FOO_ASSERT_MODE
#endif
It would be easy enough to plug in other variants, such as a version
that throws a subclass of std::logic_error. And if the header starts
to get much more complex than that, and if there are lots of separate
FOOs (e.g. a FOO_ASSERT, a BAR_ASSERT, a BAZ_ASSERT), then refactor
the common code into a single place.
If necessary, I might even have multiple FOO_ASSERT macros in a single
component: e.g. FOO_DEBUG_ASSERT, FOO_PRECONDITION_ASSERT,
FOO_EXPENSIVE_ASSERT. No only do they make it very clear to anyone
reading the code what the purpose of the assert is, they also
acknowledge that a 'one size fits all' approach to enabling and
disabling assertions is rarely sufficient for a large, real-world
application.
If the application has global a config.h-type header, then it's easy
enough to define FOO_ASSERT_MODE in there. Otherwise, define it
globally in the build system and pass it on the compiler command
line. The standard NDEBUG macro is still available if it happens to
be useful.
Richard Smith
Well, I'm not sure I agree with this. The assertions I've seen and
used are usually very lightweight and have no side-effects. Unless
they are present in a section of code that's highly cpu-bound, they
don't even register on a profiler. If they do, then they're candidates
for being compiled out in production.
--
Jonathan Mcdougall
To me the names "assert" and "expensive_assert" imply the macro will have
no semantic affect on a correct program. ENSURE or VERIFY don't imply
that. So code like:
ASSERT( ++i > 0 );
is a bug (because whether the side effect happens depends on the build),
but:
ENSURE( ++i > 0 );
is not a bug because the expression is guaranteed to be evaluated even in
NDEBUG builds.
-- Dave Harris, Nottingham, UK.
> On 03/23/11 03:41 PM, Zeljko Vrba wrote:
>> On 2011-03-22, Ian Collins<ian-...@hotmail.com> wrote:
>>>
>>> Given the above, I assume you don't leave assertions enabled in the
>>> "release" version. So you can never test the handling of the returned
>>> "meaningless answer"?
>>>
>> Other people on the team run release versions. The "never" there was
>> not absolute either; during the *development phase* I never run release
>> version.
>
> My point was if you never run a release version during the
> development phase, how can you test the error handling code? It will
> never be reached because the assert will fire. You wrote
>
> assert(condition); if (!condition) { return meaningless_answer; }
>
> so the if will never be reached unless asserts are disabled.
Isn't the whole point of assertions that once one hits, the code is
fixed so that the assertion doesn't hit? Which means the conditional
code here can't be tested, because it only happens in unknown
circumstances -- all known circumstances don't get there.
Gerhard
--
If the checks you are doing are too fast, I'm sure you could add more.
The kind of checks I have in mind include walking large tree structures
and verifying their consistency.
assert( is_valid() );
where is_valid() can do an arbitrarily large amount of work.
-- Dave Harris, Nottingham, UK.
It is thanks to MSVC that pops up dialog with ignore button so you
have to handle it anyway. As result some developers feel like assert
is some sort of conditional breakpoint that you remove from release
builds.
Correct is that either you handle the problematic case (then there is
no point to assert) or you assert that the problematic case never
happens (then there is no point to handle the case).
The contract of a function should already clearly say about a
situation that it returns something special or that it throws or that
the situation is forbidden (so it likely asserts).
Okay...
> #define ASSERT_MODE_NONE 0
> #define ASSERT_MODE_ABORT 1
> #define ASSERT_MODE_CASSERT 2
>
> #if FOO_ASSERT_MODE == ASSERT_MODE_NONE
> # define FOO_ASSERT(expr) ((void)(expr))
> #elif FOO_ASSERT_MODE == ASSERT_MODE_ABORT
> # include <stdlib.h>
> # define FOO_ASSERT(expr) ((void)(expr), abort())
> #elif FOO_ASSERT_MODE == ASSERT_MODE_CASSERT
> # include <assert.h>
> # define FOO_ASSERT(expr) assert(expr)
> #else
> # error Unknown value of FOO_ASSERT_MODE
> #endif
So where is FOO_ASSERT_MODE defined at? If it's defined by the
makefile, then your fine-grained control is clearly an SCM problem.
If it is defined in a source file, then you still have an SCM problem
-- e.g. if one of these macros is invoked in a header file, two files
could compile it differently leading to different instantiations of
the same code.
And frankly, if I have to evaluate preprocessor logic like the above
in my head in order to guess whether a chunk of unfamiliar code is
going to be executed (and what the possible outcomes are), there's
something profoundly wrong with the design of the software. God help
you if the code happens to involve side-effects. In that direction
lies madness...
> No only do they make it very clear to anyone
> reading the code what the purpose of the assert is, they also
> acknowledge that a 'one size fits all' approach to enabling and
> disabling assertions is rarely sufficient for a large, real-world
> application.
There are debug assertions (aka 'sanity checks') and there are runtime
exceptions. Either the code execution should be allowed to continue
or it shouldn't. What else is there?
Mike
write code to output something to log
run the application
inspect the log file
manually put (conditional) breakpoint at problematic place(s) rerun and debug
to
put assertion into code run the application
get immediatelly dropped into the debugger if the assert is triggered
There is also "set next statement" in VS so I can "persuade" my assert function
to return, so the execution continues and the handler code is executed :)
I think the problem(?) isn't that you can continue with MSVC's ASSERT,
the problem is that some devolpers view an ASSERT as "should not
happen", where it actually (can/should/must?) say "must not happen".
> Correct is that either you handle the problematic case (then there is
> no point to assert) or you assert that the problematic case never
> happens (then there is no point to handle the case).
Well, the should-not-happen kind of assert (that is compiled out for
customer release builds) can signal to a developer (or tester) that he's
hit a problematic point another developer missed, and that this needs
fixing.
However, there's not neccessarily any need to crash the whole app at the
customer site, just because a function in a sub-module hit a case that
wasn't expected.
So I think, depending on how well an app is/has to be tested, it can be
valid to have assert + check:
void FooDisplay::updateTableHeader() {
ASSERT(m_pData && "This function should only be called after data has
been attached - something's wrong here!")
if(m_pData) {
// do stuff
}
}
Better to not have this function crash the whole app, better to just do
nothing and (maybe) have a messed up display.
In my real world there's too little testing and too little code review,
so I think the above is a valid practical example for some/many(?) teams.
cheers,
Martin
The point of an assertion is to document a condition that is assumed to
be true at a particular place in a program, and to do so in a way that
allows this condition to be verified at runtime.
The process of verification is presumed to be expensive, and so it
conventionally performed only in "debug builds" (however we may define
that).
Now, there are (of course) conditions that should *always* be checked at
runtime because if they are not met then the program is will not
function as intended and may crash, corrupt data, fire missiles,
whatever. These checks clearly must not be omitted from "release builds"
(by which I mean code that will be run on a "live system" -- where the
consequences of continuing when the condition is not met will be
serious).
The point I've been making here is that the convention (enshrined in
assert.c for at least the last couple of decades [1]) that assertions
only generate code in debug builds is sufficiently widely known that
anyone who flouts that convention by using assertions (specifically any
function or macro with "assert" (in any lexical case) in its name) to
express conditions that MUST be checked at runtime in release builds
will be writing code that is somewhat obscure to readers who expect the
convention to be honoured.
So, to address the points above more directly: Yes, it is presumed that
when assertions in debug builds fire the developer(s) will take not and
fix the code so that the assertion condition is always met. Assertions
*should* not be allowed happen in release builds.
However, the operation of a program depends not only on the code but
also on the data, and it is (unfortunately) always possible that a
particular set of data encountered "in the field" leads to a state in
the running program that was not encountered in testing. It is therefore
often necessary to have some runtime checks that remain active in
release builds. It is right and proper to have these checks but -- I
would say -- inappropriate to call them assertions; partly because of
the widespread convention that the term "assertion" is used for
conditions that are NOT checked at runtime in release builds and partly
because we may desire different behaviour from the code when these
checks fail from that which we desire when a debugging assertion does.
Let's return to Zeljko's code snippet:
assert(condition);
if (!condition) { return meaningless_answer; }
The point here is that the code
return meaningless_answer;
will never be executed in a debug build because the assertion will
trigger, so it (and the code paths that depend on the value
meaningless_answer being returned) will only ever run in a release build
(and then only if condition is unexpectedly false), and so cannot have
been tested.
That's only true, however, if we take
assert(condition);
to be an invocation of the assert() from assert.h. That code is however
-many years old, and we've all learnt a lot about programming in general
and error handling in particular in the intervening years. If we assume
that the assert() of which Zeljko writes does not always just bomb out,
but may offer some chance to continue the execution even in debug builds
then the error-handling code paths can indeed be tested.
My own preference is for
REQUIRE( condition );
where REQUIRE (a macro) is implemented as (pseudocode here)
ASSERT( condition )
if( !condition )
{
handle error
}
> As result some developers feel like assert is some sort of
> conditional breakpoint that you remove from release builds.
ASSERT can be a conditional breakpoint, and much more. You are free to
implement assertions in any way that eases and assists your development
process. I don't do much MFC code these days (I need to target other
platforms as well as Windows) but I use that trick and others like it in
my ASSERT macros because it is a useful and productive device.
[1] Can anyone tell me when assert.h, and in particular its use of
NDEBUG to disable assertion-checking code, was first added to C? It is
described in the second edition of K&R (1988) but not -- as far as I can
see -- the first (1978).
--
Cheers,
Daniel.
[Lest there be any confusion: I am NOT the Boost maintainer of the same
name]
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
I already discussed this in the last paragraph of my post.
> If it's defined by the
> makefile, then your fine-grained control is clearly an SCM problem.
Who knows? 'SCM problem' is such a broad a term as to be pretty much
meaningless.
Virtually all build systems, even for a multi-million line projects,
have in either a list of relevant build parameters. Some of those
will be macros, some will be compiler settings, some may control what
functionality to include in that particular build. These may be in a
single place for the whole project, or they may be split up by
subsystem, by category, or similar. I've yet to see a scalable build
system that didn't make it easy to change these.
It's incredibly easy to then go to the relevant Makefile (or
whatever), locate the line that says
FOO_ASSERT_MODE := ASSERT_MODE_NONE
and change it to something else. Start the build going, and
everything else will be taken care of. (Even sorting out the
dependency analysis is easy in this case: just make the header that
defines FOO_ASSERT depend on the Makefile that defines
FOO_ASSERT_MODE.)
> If it is defined in a source file, then you still have an SCM problem
> -- e.g. if one of these macros is invoked in a header file, two files
> could compile it differently leading to different instantiations of
> the same code.
No. Absolutely not. If it's defined in a header, then it's defined
in a header that's included by the header that defines FOO_ASSERT (or
just in that header). FOO_ASSERT_MODE is defined identically
everywhere. And if it's not
> And frankly, if I have to evaluate preprocessor logic like the above
> in my head in order to guess whether a chunk of unfamiliar code is
> going to be executed (and what the possible outcomes are), there's
> something profoundly wrong with the design of the software.
As that's really not a terribly complicated piece of preprocessor
logic, I can only assume you're saying you simply don't like the
preprocessor. Fair enough: you're entitled to your opinion. If you
prefer, all of the cases other than FOO_ASSERT_NONE can be hoisted
into a function.
> God help you if the code happens to involve side-effects. In that
> direction lies madness...
Why? As I've shown it above, unlike C's assert macro, every possible
version of the FOO_ASSERT macros evaluates the expression precisely
once. Even the FOO_ASSERT_NONE version evaluates the expression,
precisely so that any side-effects still occur. Like many people, I
expect, I've been bitten by code like:
assert( *++p == '\0' );
As shown above, this is safe (though still not recommended) with
FOO_ASSERT. In actual fact, I tend to have two versions of that case:
one that evaluates the expression, but ignores its value, and one that
doesn't even evaluate it. Assuming I'd decided it was appropriate to
remove assertions from a particular build, I would tend to have
FOO_ASSERT continue to evaluate its argument, but things like
FOO_DEBUG_ASSERT and FOO_EXPENSIVE_ASSERT (which are often disabled
even during development) would usually be removed entirely. But
that's a decision that needs to be made with knowledge of how the
macros are actually used in the sub-system in question.
>> No only do they make it very clear to anyone
>> reading the code what the purpose of the assert is, they also
>> acknowledge that a 'one size fits all' approach to enabling and
>> disabling assertions is rarely sufficient for a large, real-world
>> application.
>
> There are debug assertions (aka 'sanity checks') and there are runtime
> exceptions. Either the code execution should be allowed to continue
> or it shouldn't. What else is there?
A lot of things. In addition to continuing or aborting, I might want
to throw and attempt to unwind the stack. And if I want to exit, do I
want to use abort() or exit() -- i.e. do I want to try rudimentary
clean-up of static objects, that might include resources that the OS
won't release on process termination? Do I write something to cerr/
stderr first? If there's a GUI, should I try to present something to
the user, perhaps in a message box (as Visual Studio's ASSERT tends to
do). Perhaps I should try to write something to syslog? If the
subsystem is a library that's used in several different applications,
all of these behaviours may be desirable in different cases. It's
also possible that I want to log the assertion somewhere and then
continue. Whether that's a sensible option will depend on exactly how
the assertion is used, and the consequences of running with a possibly
corrupt object / stack / database versus the consequences of just
crashing out. I may also want to note the assertion, continue, and
then exit once the program has reached a less critical point.
At one point or another, I've had occasion to use each of these
models: sometimes several in a single code-base. There really, really
isn't a single right answer to this. But if you only tend to write
code for a particular domain, it's easy to fall into the trap of
thinking that what applies to the vast majority of your own code
applies to the vast majority of all code. This is an example of a
situation where that's not the case, and I would suggest that way over
half the correspondence in this thread is due to people failing to
realise that.
Richard Smith